implementation of operator overload (+=) in ruby [duplicate] - ruby

This question already has an answer here:
+= operator overloading in ruby
(1 answer)
Closed 3 years ago.
how to implement operator overloading (operator += )for two one-dimensional arrays? for b+=a in method def +# ??? end
class Vector
def initialize
#vector = Array.new
end
def set_vector=(vector)
#vector = vector
end
def get_vector
#vector
end
def +#
?????
end
end
a = Vector.new
a.set_vector = [1,3,4,5]
print a.get_vector
b = Vector.new
b.set_vector = [1,2]
print b.get_vector
a.get_vector += b.get_vector
puts "\n a new"
puts a

There's a more Ruby way of doing this that fixes the issue:
class Vector
attr_accessor :vector
def initialize(*values)
#vector = values
end
def [](i)
#vector[i]
end
def +(vector)
Vector.new(
*#vector.map.with_index do |v, i|
v + vector[i].to_i
end
)
end
end
Note that + is just a regular method, there's no +# method involved here, that's the unary + as in r = +v, but you want to return a new object so you can chain it, as in a + b + c, and never modify the original.
+= creates a new object and does the assignment, as in x = x + 1.
In practice:
v = Vector.new(1,2)
r = v + Vector.new(2,3)
# => #<Vector:0x00007fc6678955a0 #vector=[3, 5]>

Related

Why do I get ArgumentError when sorting floats contained within an object in Ruby?

I can sort a list of floats, no problem. But if I'm trying to compare floats in an object to sort objects in a list, I get this error:
`sort': comparison of HateRuby with HateRuby failed (ArgumentError)
Here's some code:
class HateRuby
attr_reader :aFloat
attr_writer :aFloat
def initialize(f)
#aFloat = f
end
end
puts "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}\n\n"
x = []
x << HateRuby.new(3.3)
x << HateRuby.new(2.2)
x << HateRuby.new(1.1)
puts "x contents:"
x.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
x.sort { |a,b| a.aFloat <=> b.aFloat }
y = x.sort
puts "y contents:"
y.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
This produces:
[path]/Ruby/rb3D54.tmp:21:in `sort': comparison of HateRuby with HateRuby failed (ArgumentError)
from [path]/Ruby/rb3D54.tmp:21:in `<main>'
1.9.3-p125
x contents:
3.30: Float
2.20: Float
1.10: Float
Complete(1)
I don't really hate Ruby, of course, but I am annoyed...
Thanks to anyone listening.
y = x.sort causing the error, as #sort compares object using the method <=>. But you didn't define the method. There is no HateRuby#<=> method in your class HateRuby.
While you would write collection_object.sort, an implicit block has been supplied, like collection_object.sort { |a,b| a <=> b }. This way sorting is being done.
Now see it is working :
class HateRuby
attr_reader :aFloat
attr_writer :aFloat
def initialize(f)
#aFloat = f
end
def <=>(ob)
# just for clarity I use `self`, you can remove it. as it is implicit.
self.aFloat <=> ob.aFloat
end
end
x = []
x << HateRuby.new(3.3)
x << HateRuby.new(2.2)
x << HateRuby.new(1.1)
x.sort
# => [#<HateRuby:0x9e73d2c #aFloat=1.1>,
# #<HateRuby:0x9e73d40 #aFloat=2.2>,
# #<HateRuby:0x9e73d54 #aFloat=3.3>]
You have to implement the method <=> from the Mixin Comparable:
include Comparable
and
def <=> (other)
You were very close. The sort method does not sort the array itself, it delivers a sorted copy. You have to assign a variable to it. This is your code with one line changed and one line gone:
class HateRuby
attr_reader :aFloat
attr_writer :aFloat
def initialize(f)
#aFloat = f
end
end
puts "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}\n\n"
x = []
x << HateRuby.new(3.3)
x << HateRuby.new(2.2)
x << HateRuby.new(1.1)
puts "x contents:"
x.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
y = x.sort { |a,b| a.aFloat <=> b.aFloat } # <== This line changed
# y = x.sort # <== This line removed
puts "y contents:"
y.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }

Ruby Class Instance Variable not cleared between method call : Integer class

I have the following code for an assignment. After much debugging I found what was happening
class Integer
def initialize()
#ans = ""
end
def ans
#ans = ""
end
def ans=(value)
#ans = value
end
def to_base(base)
# convert given number into the base
# figure out how to make it the most efficient
num = self
r = 0
loop do
r = num % base # modulus
#ans = r.to_s + #ans.to_s # add to answer
num /= base # division
break unless num != 0
end
english = #ans # return value
end
def to_oct
self.to_base(8)
end
end
puts 8.to_oct
puts 8.to_base(2)
Output:
10
100010
The output for the binary version should be 1000 not 100010
What it did was append the first instance of the class 8.to_oct onto the second call 8.to_base(2)
Is there a way to have this cleared as I want to use the same number (8) in this example and convert it to various base numbers. what am I doing wrong in my class?
Thanks!

Pass parameters to passed-in block in Ruby

I want to pass a block to a function, and then call that block with some additional parameters as follows:
def foo(&block)
some_array = (1..3).to_a
x = 7 # Simplified
result = some_array.map &block # Need some way to pass in 'x' here
end
def a_usage_that_works
foo do |value|
value
end
end
def a_usage_that_doesnt_work
foo do |value, x|
x # How do I pass in x?
end
end
# rspec to demonstrate problem / required result
describe "spike" do
it "works" do
a_usage_that_works.should == [1,2,3]
end
it "doesn't work" do
a_usage_that_doesnt_work.should == [7, 7, 7]
end
end
How can I pass in the additional parameter to the block?
Create another block and call first one from it.
def foo(&block)
some_array = (1..3).to_a
x = 7 # Simplified
result = some_array.map {|elem| block.call(elem, x)}
end
You pass to the block by yielding to it.
def foo(&block)
some_array = [1,2,3]
x = 7
some_array.map{|el| yield el, x}
end
p foo{|p1, p2| p2} #=>[7,7,7]
p foo{|p1, p2| p1} #=>[1,2,3]
You can use a higher-order function to generate a simplified function:
Let's assume that the block we pass to foo will accept value, x.
Naive strategy, using an inline-defined x:
def foo(&block)
some_array = (1..3).to_a
x = 7
simple_func = proc {|value| block.call(value, x) }
result = some_array.map &simple_func
end
Strategy using separation of concerns:
def get_simple_func(block)
# This assumes x won't change per iteration.
# If it can change, you can move the calculation inside the proc.
# Moving it inside also allows the calculation to depend on "value", in case you want that.
x = complex_calculation_for_x()
proc {|value| block.call(value, x) }
end
def foo(&block)
some_array = (1..3).to_a
simple_func = get_simple_func(block)
result = some_array.map &simple_func
end
Obviously you shouldn't use this when x is a literal value because it would be over-engineering. But as the calculation of x becomes more complex, separating it out makes the code more readable. Also, foo can focus on the specific task of applying the function to some_array.

Refactoring feedback for Reverse Polish Notation (RPN) or Postfix Notation

One of the pre-work exercises for Dev Bootcamp is an RPN calculator. I made it work but would like refactoring feedback. Any and all help to make this code cleaner is greatly appreciated.
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
b = array.pop(2)
case
when i == "+" then array << b[0] + b[1]
when i == '-' then array << b[0] - b[1]
when i == '*' then array << b[0] * b[1]
when i == '/' then array << b[0] / b[1]
end
end
end
p array.pop
end
end
calc = RPNCalculator.new
calc.evaluate('1 2 +') # => 3
calc.evaluate('2 5 *') # => 10
calc.evaluate('50 20 -') # => 30
calc.evaluate('70 10 4 + 5 * -') # => 0
class RPNCalculator
def evaluate rpn
array = rpn.split(" ").inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
b = array.pop(2)
array << b[0].send(i, b[1])
end
end
p array.pop
end
end
I tend to prefer avoiding case..when in favor of lookup tables. So I'd change your code to:
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
array << array.pop(2).reduce(op(i))
end
end
p array.pop
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I also don't believe you should only be popping off 2 operands. "1 2 3 +" would be valid RPN, evaluating to 6. The entire stack should be reduced. This also avoids the mutation, which is a good thing, as it follows a more functional style.
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
[*array, i.to_i]
else
[array.reduce(op(i))]
end
end
p array.pop
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I removed the other mutation here too, by using [*arr, value] instead of actually modifying the array.
Finally, I'd avoid printing directly from your #evaluate method and just return the number. I'd also (again) avoid the mutation:
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
stack = a.inject([]) do |stack, i|
if i =~ /\d+/
[*stack, i.to_i]
else
[stack.reduce(op(i))]
end
end
stack.last
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I renamed 'array' to 'stack', since it is a parser stack and is less generic than just array.

How to handle combination []+= for auto-vivifying hash in Ruby?

In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.has_key?k
super(k)
else
AutoHash.new(:update => self, :update_key => k)
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects in Ruby?
There is no way to define a []+= method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the [] and []= methods are called on x (and + is called on x[y], which in this case is an AutoHash). I think that the best way to handle this problem would be to define a + method on AutoHash, which will just return it's argument. This will make AutoHash.new[:x] += y work for just about any type of y, because the "empty" version of y.class ('' for strings, 0 for numbers, ...) plus y will almost always equal y.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
#update, #update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }

Resources