The problem is here:
case arr
when arr[i] == "one"
num += 1
# ...
end
In a case
statement, the value you give after each when
will be compared to the value you give after case
. In other words, your case
statement will first do (arr[i] == "one") === arr
, then (arr[i] == "two") === arr
, and so on. Since arr[i] == "one"
returns true
or false
, and true === arr
and false === arr
will never be true, none of the conditions in your case
statement are ever met.
The simplest fix is this:
case arr[i]
when "one"
num += 1
when "two"
num += 2
when "three"
num += 3
# ...
end
What this code does is compare the value of arr[i]
to "one"
, then "two"
, and so on, which yields the intended behavior.
Note also that I removed exit
. In Ruby, exit
exits the program. I'm guessing you were trying to use it like you'd use break
in other programming languages. You don't need that here, because Ruby case
statements don't have "fall-through" like switch
does in some languages. Only one when
will match in a case
statement.
More improvements
I have a few more notes on your code:
This code doesn't do anything:
if !arr.any?
0
end
You don't do anything with that 0
, so it just disappears into the void. You probably meant to do return 0
to return from the method early. A better idiom would have been this:
return 0 if arr.empty?
while
is unnecessary here In Ruby it's much more common to use Enumerable#each
to iterate over elements of an array:
arr.each do |word|
case word
when "one"
# ...
# ...
end
end
You can make your case
statement much more concise by treating it as an expression that returns a value:
num += case arr[i]
when "one" then 1
when "two" then 2
when "three" then 3
# ...
else 0
end
You can make this more concise still by using a Hash instead of a case
statement:
word_values = {
"one" => 1, "two" => 2, "three" => 3,
"four" => 4, "five" => 5, "six" => 6,
"seven" => 7, "eight" => 8, "nine" => 9
}
word_values.default = 0
arr.each do |word|
num += word_values[word]
end
This iterates over the array of words and looks up each one in the word_values
hash. If the word isn't in the hash it will return the default value, 0
.
With the word_values
hash you could also do the following:
values = arr.map {|word| word_values[word] }
puts values.reduce(:+)
This uses map
to transform the array of words into an array of values, for example "five monkeys in three farms"
becomes [5, 0, 0, 3, 0]
. It then uses reduce
(also called inject
) to calculate the sum of the values.
All together now
Put all of those improvements together and we have this:
WORD_VALUES = {
"one" => 1, "two" => 2, "three" => 3,
"four" => 4, "five" => 5, "six" => 6,
"seven" => 7, "eight" => 8, "nine" => 9
}
WORD_VALUES.default = 0
def secret_code(str)
str.split(/\W+/)
.map {|word| WORD_VALUES[word] }
.reduce(:+) || 0
end
puts secret_code("one on one") # => 2
puts secret_code("five monkeys in three farms") # => 8
puts secret_code("") # => 0
puts secret_code("fivethreeone") # => 0
(In Ruby 2.3 you can actually do .map(&WORD_VALUES)
, which is pretty neat.)
And here's another approach, which is slightly more efficient:
def secret_code(str)
str.split(/\W+/).reduce(0) {|sum, word| sum + WORD_VALUES[word] }
end
It uses reduce
in its block form to look up each value in the hash and add it to a running sum
.
An alternative
This is slightly more "advanced," but shows off some other Ruby features:
WORD_VALUES = {
"one" => 1, "two" => 2, "three" => 3,
"four" => 4, "five" => 5, "six" => 6,
"seven" => 7, "eight" => 8, "nine" => 9
}
WORD_EXPR = /\b#{Regexp.union(WORD_VALUES.keys)}\b/
def secret_code(str)
str.scan(WORD_EXPR).reduce(0) {|sum, word| sum + WORD_VALUES[word] }
end
This uses the keys of the WORD_VALUES
array to build a regular expression that looks (more or less) like this:
WORD_EXPR = /\b(one|two|three|four|five|six|seven|eight|nine)\b/
Then it uses String#scan
, which returns all of the substrings that match the regular expression, so e.g. "five monkeys in three farms".scan(WORD_EXPR)
returns ["five", "three"]
. Then it uses reduce
as above to look up their values and sum them.