I am trying to do this lesson and I am clearly missing something glaringly obvious!
require "rpn_calculator" describe RPNCalculator do attr_accessor :calculator before do @calculator = RPNCalculator.new end it "adds two numbers" do calculator.push(2) calculator.push(3) calculator.plus calculator.value.should == 5 end ... # extra credit it "evaluates a string" do calculator.evaluate("1 2 3 * +").should == ((2 * 3) + 1) ... end end
Everything is working to my novice eyes except @numbers. setting @numbers from the evaluate method doesn't affect @numbers in other methods and I really don't understand why. I've tried everything I could google including changing @numbers to @@numbers but nothing seems to help. I could just evaluate the string in the evaluate method... but I already have such a nice plus method that I can use!
class RPNCalculator attr_accessor :numbers def initialize @numbers = [] end def push(n) @numbers.push(n) end def plus @numbers.length > 1 ? @numbers.push(@numbers.pop(2).reduce(:+) ) : fail end def minus @numbers.length > 1 ? @numbers.push(@numbers.pop(2).reduce(:-) ) : fail end def divide @numbers.length > 1 ? @numbers.push(@numbers.pop(2).inject{|x,y| x.to_f / y} ) : fail end def times @numbers.length > 1 ? @numbers.push(@numbers.pop(2).reduce(:*) ) : fail end def value @value = @numbers[-1] end def tokens(pol) pol.split(' ').map{|n| n.to_i.to_s == n ? n.to_i : n.to_sym} end def evaluate(pol) order = [] opps = {:+ => plus, :- => minus, :/ => divide, :* => times } tokens(pol).reverse.chunk{|n| n.is_a?(Integer)}.each{|e,a| e == true ? a.reverse.each{|a| push(a) } : a.each {|a| order.push(a) }} order.reverse.each {|o| (opps[o]) } end def fail begin raise Exception.new("calculator is empty") end end end
The result is the plus returns fail because @numbers is empty....
RPNCalculator adds two numbers adds three numbers subtracts the second number from the first number adds and subtracts multiplies and divides resolves operator precedence unambiguously fails informatively when there's not enough values stacked away tokenizes a string evaluates a string (FAILED - 1) Failures: 1) RPNCalculator evaluates a string Failure/Error: calculator.evaluate("1 2 3 * +").should == Exception: calculator is empty # ./12_rpn_calculator/rpn_calculator.rb:59:in `fail' # ./12_rpn_calculator/rpn_calculator.rb:14:in `plus' # ./12_rpn_calculator/rpn_calculator.rb:39:in `evaluate' # ./12_rpn_calculator/rpn_calculator_spec.rb:134:in `block (2 levels) in '
Thanks to Frank Schmitt I got it working. Apparently one does not simply store methods in hashes.
Correct evaluate method:
def evaluate(pol) @numbers = [] # because this does 4 tests without clearing @numbers opps = {:+ => Proc.new {plus}, :- => Proc.new{minus}, :/ => Proc.new{divide}, :* => Proc.new{times} } # method in proc, thank you Frank :D tokens(pol).chunk{|n| n.is_a?(Integer)}.each{|e,a| e == true ? a.each{|a| push(a) } : a.each {|o| (opps[o].call) }} @numbers[0] end
Apparently, Ruby calls the methods as soon as you store them inside your opps hash. To fix this, you can use this modified version of your code:
def evaluate(pol)
order = []
opps = {:+ => Proc.new {plus}, :- => Proc.new{minus}, :/ => Proc.new{divide}, :* => Proc.new{times} }
tokens(pol).reverse.chunk{|n| n.is_a?(Integer)}.each{|e,a| e == true ? a.reverse.each{|a| push(a) } : a.each {|a| or
der.push(a) }}
order.reverse.each {|o| (opps[o].call) }
end
This version