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