-->

ruby: adding numbers and printing true when sums a

2019-07-28 17:15发布

问题:

It's a simple problem given on rubeque.com: write a method that takes any number of integers and adds them to return true if the sum is 21. False otherwise. It tests the input with:

assert_equal twenty_one?(3, 4, 5, 6, 3), true
assert_equal twenty_one?(3, 11, 10), false

Here's what I have so far:

def twenty_one?(*nums)
  nums.inject(&:+)
end

if twenty_one? == 21 
  puts true
else 
  false
end    

But I get the error message:

RuntimeError: The value '21' does not equal 'true'.

I'm really confused on how to fix this. Is it possible to put the if/else statement within the method? And sorry if this question is really basic. I'm new to programming.

回答1:

You need to write your method as

def twenty_one?(*nums)
  nums.inject(&:+) == 21
end

Here is one little demo :-

require "minitest/autorun"

class Numerics
  def twenty_one?(*nums)
    nums.inject(&:+) == 21
  end
end

class TestNumerics < MiniTest::Test
  def setup
    @numeric = Numerics.new
  end

  def teardown
    @numeric = nil
  end

  def test_twenty_one?
    assert_equal @numeric.twenty_one?(3, 4, 5, 6, 3), true
    assert_equal @numeric.twenty_one?(3, 11, 10), false
  end
end

Let's run the tests :-

[arup@Ruby]$ ruby test/test_numerics.rb
Run options: --seed 61602

# Running:

.

Finished in 0.001332s, 750.9402 runs/s, 1501.8804 assertions/s.

1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
[arup@Ruby]$

In your method, it was returning Fixnum instance 21, which you were trying to compare with true. That's why you got the error. If you look at the source of assert_equal, you will find the comparisons between 2 objects which are the instances of the same class, otherwise it will throw the error you got.

Note: You of-course can write this nums.inject(&:+) as nums.inject(:+) too as Ruby allows this freedom in case of #reduce/#inject method particularly out of the box.

Update

Carles Jove Buxeda gave one nice idea to design this problem. The idea is to put the methods inside the module, and then include it to test its methods:

require "minitest/autorun"

module Numerics
  def twenty_one?(*nums)
    nums.inject(:+) == 21
  end
end

class TestNumerics < MiniTest::Test
  include Numerics

  def test_twenty_one?
    assert_equal twenty_one?(3, 4, 5, 6, 3), true
    assert_equal twenty_one?(3, 11, 10), false
  end
end

Now if I run it :

arup_ruby$ ruby test/test_numerics.rb
Run options: --seed 1223

# Running:

.

Finished in 0.001067s, 937.2071 runs/s, 1874.4142 assertions/s.

1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
arup_ruby$

Very cool idea!



回答2:

Arup's answer is great. I would suggest to make it a little more generic.

def twenty_one?(*nums)
  nums.inject(&:+) == 21
end

def compare_value_with_sum_of_nums?(value, *nums)
  nums.inject(&:+) == value
end

I am really bad with names. This made me think it would be pretty cool to combine your method name with metaprogramming and the gem humanize. You could then have dynamic method names and infer from the method name what value the sum should add up to. So you could have any comparison, like

MyAwesomeGem.three_thousand_and_twenty_four?(3000, 20, 4)

Would be fun to build!

:)



标签: ruby sum splat