question on overriding + operator in ruby - ruby

A recent convert to Ruby here. The following question isn't really practical; it's more of a question on how the internals of Ruby works. Is it possible to override the standard addition operator to accept multiple inputs? I'm assuming that the answer is no, given that the addition operator is a standard one, but i wanted to make sure i wasn't missing something.
Below is the code I wrote up quick to verify my thoughts. Note, it's completely trivial/contrived.
class Point
attr_accessor :x, :y
def initialize(x,y)
#x, #y = x, y
end
def +(x,y)
#x += x
#y += y
end
def to_s
"(#{#x}, #{#y})"
end
end
pt1 = Point.new(0,0)
pt1 + (1,1) # syntax error, unexpected ',', expecting ')'

You should not mutate the object when implementing + operator. Instead return a new Point Object:
class Point
attr_accessor :x, :y
def initialize(x,y)
#x, #y = x, y
end
def +(other)
Point.new(#x + other.x, #y + other.y)
end
def to_s
"(#{#x}, #{#y})"
end
end
ruby-1.8.7-p302:
> p1 = Point.new(1,2)
=> #<Point:0x10031f870 #y=2, #x=1>
> p2 = Point.new(3, 4)
=> #<Point:0x1001bb718 #y=4, #x=3>
> p1 + p2
=> #<Point:0x1001a44c8 #y=6, #x=4>
> p3 = p1 + p2
=> #<Point:0x1001911e8 #y=6, #x=4>
> p3
=> #<Point:0x1001911e8 #y=6, #x=4>
> p1 += p2
=> #<Point:0x1001877b0 #y=6, #x=4>
> p1
=> #<Point:0x1001877b0 #y=6, #x=4>

You can define the + method like that, but you'll only be able to call it using normal method call syntax:
pt1.+(1,1)

You can achieve something similar using arrays:
def +(args)
x, y = args
#x += x
#y += y
end
and later use it as:
pt1 + [1, 1]
You can also combine it with Chandra's solution, to accept both arrays and Points as arguments.

Related

`initialize': wrong number of arguments (given 3, expected 0) (ArgumentError)

I have the following bit of code:
load 'Point.rb'
class Triangle
def initialize(x, y , z)
if !x.is_a?(Point) || !y.is_a?(Point) || !z.is_a?(Point)
puts "Invalid data, a Triangle can only be initialized through points"
else
#x=x
#y=y
#z=z
#type=type(x, y, z)
end
end
def type(x, y, z)
if x.distance(y) == y.distance(z) && y.distance(z) == z.distance(x)
return "equilateral"
elsif x.distance(y)== y.distance(z) || y.distance(z) == z.distance(x) || z.distance(x) == x.distance(y)
return "isosceles"
else
return "scalene"
end
end
attr_accessor :type
end
I'm calling the method like this:
load 'Triangle.rb'
x=Point.new(0,0)
y=Point.new(1,1)
z=Point.new(2,0)
triangle=Triangle.new x, y, z
puts triangle.type
The class Point is as follows:
class Point
def initialize (x=0, y=0)
#x=x.to_i
#y=y.to_i
end
def ==(point)
if #x==point.x && #y==point.y
return true
else
return false
end
end
def distance(point)
Math.hypot((#x-point.x),(#y-point.y))
end
attr_accessor :y
attr_accessor :x
end
The error is as follows:
Triangle.rb:11:in `initialize': wrong number of arguments (given 3, expected 0) (ArgumentError)
from Triangle_test.rb:6:in `new'
from Triangle_test.rb:6:in `<main>'
Please tell if the whole code is just garbage.
In your Triangle class you have method type which accepts three parameters, and then below you have attr_accessor :type which overwrites that 3-parameters method with a parameterless getter.
That's why you get that error when you do this in the initializer
#type=type(x, y, z)
Here's a cleaned-up version of your code:
removed unneeded if's
removed unneeded return's
defined a private calculate_type method
replaced attr_accessor with attr_reader
renamed x,y,z with a,b,c to avoid confusion between coordinates and points
class Point
attr_reader :x, :y
def initialize(x = 0, y = 0)
#x = x.to_i
#y = y.to_i
end
def ==(point)
#x == point.x && #y == point.y
end
def distance(point)
Math.hypot((#x - point.x), (#y - point.y))
end
end
class Triangle
attr_reader :a, :b, :c, :type
def initialize(a, b, c)
raise 'Invalid data, a Triangle can only be initialized through points' unless [a, b, c].all? { |p| p.is_a?(Point) }
#a, #b, #c = a, b, c
#type = calculate_type
end
private
def calculate_type
if a.distance(b) == b.distance(c) && b.distance(c) == c.distance(a)
'equilateral'
elsif a.distance(b) == b.distance(c) || b.distance(c) == c.distance(a) || c.distance(a) == a.distance(b)
'isosceles'
else
'scalene'
end
end
end
a = Point.new(0, 0)
b = Point.new(1, 1)
c = Point.new(2, 0)
triangle = Triangle.new a, b, c
puts triangle.type
# isosceles

What is the correct way to write this in Ruby?

I am needing to write few methods: value(x), zero(a,b,e), area(a,b), derivative(x)
class Funkcja
def initialize(funkcja)
#funkcja = funkcja
end
def value(x)
#funkcja.call(x)
end
end
This class will have to work over block which is an object from Proc
This is how I create that new object
f = Funkcja.new (Proc.new{|x| x*x*Math.sin(x)})
What is the correct way and in Ruby style (if not please show me that i
newbie in Ruby) to do this right Funkcja.new (Proc.new x) and
initialize #funkcja = funkcja
def zero(a, b, eps)
x = (a+b)/2.0
val = value(x)
if val >= -eps and val <= eps
x
else
left = value(a)
rigth = value(b)
if left < 0 and val > 0
zero(a,x,eps)
elsif left > 0 and val < 0
zero(a,x,eps)
elsif rigth > 0 and val < 0
zero(x,b,eps)
elsif rigth < 0 and val > 0
zero(x,b,eps)
elsif value == 0
x
else
nil
end
end
end
def area(a,b)
pole = 0
while a < b
if (self.value(a) > self.value( a + 0.00001))
pole = pole + (self.value( a) * 0.00001)
else
pole = pole + (self.value( a + 0.00001) * 0.00001 )
end
a += 0.00001
end
pole
end
def derivative(x)
eps = 0.00000001
return (self.value(x) - self.value(x - eps))/eps
end
Area is calculated area between a and b and OX, zero is find where
F (x)=0 derivative is calculated as derivative in point.
The main thing that's non-idiomatic is this:
f = Funkcja.new (Proc.new{|x| x*x*Math.sin(x)})
What would be more normal is to do this:
f = Funkcja.new { |x| x*x*Math.sin(x) }
This is a normal block, and you could split it up among multiple lines as usual:
f = Funkcja.new do |x|
x*x*Math.sin(x)
end
However, this wouldn't work with your initialize definition and that's because of one minor detail. You'd just need to change def initialize(funkcja) to def initialize(&funkja) - this converts the passed block into a proc that you can assign to a variable, use call with, etc:
def initialize(&funjka)
#funkja = funkja
end
Another way to do the same thing would be this:
def initialize
#funkja = yield
end
Other than that, your code seems fine with one other glaring non-idiomatic thing, which that you use self.value. The self is unnecessary unless you're using a setter method (i.e. self.value =), which you're not here.
Here's an idea of adapting one of your methods to a more Ruby style:
def area(a,b)
pole = 0
while a < b
if (yield(a) > yield(a + 0.00001))
pole = pole + (yield(a) * 0.00001)
else
pole = pole + (yield(a + 0.00001) * 0.00001)
end
a += 0.00001
end
pole
end
You'd use it like this:
area(a, b) do |a|
a ** 2
end
Now it's one thing to be handling Proc objects, that's fine, the do ... end method of appending blocks to method calls generates those by default. It's very not Ruby to put extra methods on Proc itself. I can't see anything here that can't be covered by defining these inside a simple module:
module CalculationModule
def area(a, b)
# ... Implementation
end
extend self
end
That way you can use those methods like CalculationModule.area or you can always include it into another class or module and use them like area.

Unexpected Ruby behavior when instantiating two instances on one line

I created a class that generates a different name for each instance, but a test fails unexpectedly when instantiating two instances in one statement.
Here is the class.
class Robot
attr_accessor :name
##current_name = 'AA000'
def initialize
#name = ##current_name
##current_name.next!
end
end
Here the class behaves as expected
irb(main):009:0> Robot.new.name
=> "AA001"
irb(main):010:0> Robot.new.name
=> "AA002"
Here is the unexpected behavior, I was expecting false. This code is in a test on an exercise I am trying to get passing, so I can't change the test.
irb(main):011:0> Robot.new.name == Robot.new.name
=> true
Checking the object_id reveals that two different instances are being created.
irb(main):012:0> Robot.new.object_id == Robot.new.object_id
=> false
Why is Ruby doing this, what should I do to fix it & assuming there is a term for this, what could I have typed into search to find answered questions about this.
See if this helps:
class Robot
attr_accessor :name
##current_name = 'AA000'
def initialize
#name = ##current_name
##current_name.next!
end
end
x = Robot.new
puts x.name
y = Robot.new
puts y.name
puts x.name == y.name
puts x.name
puts y.name
--output:--
AA001
AA002
true
AA002
AA002
Why is Ruby doing this
Because each instance's #name variable refers to the same String as the variable ##current_name, and you keep changing that String with the ! method.
what should I do to fix it
class Robot
attr_accessor :name
##current_name = 'AA000'
def initialize
#name = ##current_name.dup
##current_name.next!
end
end
x = Robot.new
puts x.name
y = Robot.new
puts y.name
p x.name == y.name
p x.name
p y.name
--output:--
AA000
AA001
false
AA000
AA001
Although, I and many other people will warn you NEVER to use ##variables in your code.
Ruby assignment operator:
1. x = “hello”:
x ------> “hello”
2. y = x:
x ------> “hello”
^
|
y -----------+
3. y << “ world”:
x ------> “hello world”
^ ^
| ^
y -----------+ ^
> > >
It matters not that x and y's names may be spelled #name and ##current_name.
Here is another code example:
x = "hello"
y = x
y << " world"
puts x, y
--output:--
hello world
hello world
x.next!
puts x, y
--output:--
hello worle
hello worle
Here is an example with immutable types:
1. x = 10:
x ------> 10
2. y = x:
x ---------> 10
^
|
y -----------+
3. y += 1
=> y = y + 1
=> y = 10 + 1
And 10 + 1 creates the new Integer object 11 and assigns it to y:
x ------> 10
y ------> 11
The expression 10 + 1 does NOT increment the Integer object 10 that both x and y refer to--because Integer objects are immutable.
Here is another example:
x = 10
y = x
x.next
puts x,y #=> ??
x.next creates a new Integer object 11, and because the newly created Integer object 11 is not assigned to a variable, 11 is discarded, so x and y still refer to the same Integer object 10.

Ruby instance variable changes unexpectedly

In the code below, a cluster has many points.
class Cluster
attr_accessor :centroid, :points
def initialize(centroid, *points)
#centroid = centroid
#points = points
end
end
class Point
attr_accessor :x, :y
def initialize(x = 0, y = 0)
#x = x
#y = y
end
end
An example of a Cluster object (lets us call this c):
#<Cluster:0x007ff5c123c210
#centroid=#<Point:0x007ff5c123c288 #x=25, #y=125>,
#points=
[#<Point:0x007ff5c123c238 #x=25, #y=125>,
#<Point:0x007ff5c1020120 #x=28, #y=145>]>
I am trying to calculate the mean of the points, and update #centroid without changing #points.
Let's say I have:
class Point
def +(point)
#x = #x + point.x
#y = #y + point.y
self
end
def /(num)
#x = #x/num
#y = #y/num
self
end
end
and to calculate the mean of all the points, I run:
c.centroid = c.points.reduce(&:+)/c.points.length
Then, c changes to:
#<Cluster:0x007ff5c123c210
#centroid=#<Point:0x007ff5c1515ec8 #x=26, #y=135>,
#points=
[#<Point:0x007ff5c1515ec8 #x=26, #y=135>,
#<Point:0x007ff5c1020120 #x=28, #y=145>]>
Note that first element of the #points is changed. Any suggestions?
Your + method in Point modifies the point's members #x and #y. You need to return a new point with the calculated values instead:
def +(point)
Point.new(#x + point.x, #y + point.y)
end
def /(num)
Point.new(#x/num, #y/num)
end
Since you did not pass an initial value to reduce, the first point became modified. You can pass a new point as the initial value to reduce, which will be modified and returned.
c.centroid = c.points.reduce(Point.new, &:+)/c.points.length
I think the problem is caused by the + method modifying the point #x and #y values.
Try changing the + method to:
def +(point)
x = #x + point.x
y = #y + point.y
self.class.new(x, y)
end

Setting method local variables from a proc

If I have a class with two instance variables #x and #y, I can change them from a proc using self.instance_exec:
class Location
attr_accessor :x, :y
def initialize
#x = 0
#y = 0
end
def set &block
self.instance_exec(&block)
end
end
location = Location.new
location.set do
#x = rand(100)
#y = rand(100)
end
puts location.x
puts location.y
If I have a class with a method set with two local variables x and y, I can use proc return values to set them:
class Location
def set &block
x = 0;
y = 0;
x, y = block.call()
# do something with x and y
puts x
puts y
end
end
location = Location.new
location.set do
x = rand(100)
y = rand(100)
[x, y]
end
Is there a way to access the set method local variables x and y from the proc without using return values?
You can do it, sort of, but it isn't pretty
There is a way for block to set a variable in a calling method, but it isn't pretty. You can pass in a binding, then eval some code using the binding:
def foo(binding)
binding.eval "x = 2"
end
x = 1
foo(binding)
p x # => 2
Blocks also carry with them the binding in which they were defined, so if a block is being passed, then:
def foo(&block)
block.binding.eval "x = 2"
end
x = 1
foo {}
p x # => 2
What's in the block doesn't matter, in this case. It's just being used as a carrier for the binding.
Other ways to achieve the same goal
Yield an object
A more pedestrian way for a block to interact with it's caller is to pass an object to the block:
class Point
attr_accessor :x
attr_accessor :y
end
class Location
def set
point = Point.new
yield point
p point.x # => 10
p point.y # => 20
end
end
location = Location.new
location.set do |point|
point.x = 10
point.y = 20
end
This is often preferred to fancier techniques: It's easy to understand both its implementation and its use.
instance_eval an object
If you want to (but you probably shouldn't want to), the block's caller can use instance_eval/instance_exec to call the block. This sets self to the object, for that block.
class Location
def set(&block)
point = Point.new
point.instance_eval(&block)
p point.x # => 10
p point.y # => 20
end
end
location = Location.new
location.set do
self.x = 10
self.y = 20
end
You see that the block had to use use self. when calling the writers, otherwise new, local variables would have been declared, which is not what is wanted here.
Yield or instance_eval an object
Even though you still probably shouldn't use instance_eval, sometimes it's useful. You don't always know when it's good, though, so let's let the method's caller decide which technique to use. All the method has to do is to check that the block has parameters:
class Location
def set(&block)
point = Point.new
if block.arity == 1
block.call point
else
point.instance_eval(&block)
end
p point.x
p point.y
end
end
Now the user can have the block executed in the scope of the point:
location = Location.new
location.set do
self.x = 10
self.y = 20
end
# => 10
# => 20
or it can have the point passed to it:
location.set do |point|
point.x = 30
point.y = 40
end
# => 30
# => 40

Resources