Reputation: 39
In the card game bridge, four cards are given point values: Jack: 1, Queen: 2, King: 3, Ace: 4. Given an array of strings corresponding to a hand of cards (the cards are represented like so: ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]), return the total number of high card points for that hand.
I can solve this simple problem with while
loop, but I would like to learn how to use .each
to iterate through an array as well, Here is my code that doesn't work
def high_card_points(hand)
sum = 0
hand.each do |i|
if hand[i] == "J"
sum += 1
elsif hand[i] == "Q"
sum += 2
elsif hand[i] == "K"
sum += 3
elsif hand[i] == "A"
sum += 4
end
end
sum
end
Now, when I run it, the error no implicit conversion of String into Integer
comes out. How should I do it in the right way?
Upvotes: 1
Views: 143
Reputation: 110755
The error message states, "TypeError (no implicit conversion of String into Integer)" and that the exception was raised in the line hand[i] == "J"
. The first element passed to the block by each
and assigned to the block variable i
is i = hand.first #=> "2"
. We therefore have hand["2"] == "J"
, or in fact, hand.[]("2")
, but the method Array#[] requires its argument to be an integer, and there is "no implicit conversion of String into Integer".
Let me now address a different aspect of your question.
arr = ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]
You could write the following.
arr.reduce(0) do |tot, s|
tot +
case s
when "J" then 1
when "Q" then 2
when "K" then 3
when "A" then 4
else 0
end
end
#=> 10
I can hear you. You are saying, "I said I wanted to use .each
!". Well, you have! Let me explain.
arr
is an instance of the class Array
. Array
has Module#include'd the module Enumerable, which is why we can invoke the instance method Enumerable#reduce on arr
. (Array.included_modules #=> [Enumerable, Kernel]
).
Like all other instance methods in Enumerable
, Enumerable#reduce (aka inject
) requires a receiver that is an instance of the class Enumerator, but arr
is an instance of Array
, not Enumerator
. Ruby gets around this as follows. When reduce
is invoked on arr
, she sees that arr
is not an instance of Enumerator
so she checks to see if arr
has a method each
(that is, whether arr
's class Array
has an instance method each
). It does, so she invokes each
on arr
to obtain
enum = arr.each
#=> #<Enumerator: ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J",
# "Q", "K", "A"]:each>
We now have the enumerator on which reduce
can be invoked:
enum.reduce(0) do |tot, s|
tot +
case s
when "J" then 1
when "Q" then 2
when "K" then 3
when "A" then 4
else 0
end
end
#=> 10
You don't see Array#each
being invoked, but it certainly is. We can confirm that by including Enumerable
in a class that does not have a method each
and see what happens.
class C
include Enumerable
end
c = C.new
#=> #<C:0x0000000002a118a8>
c.reduce {}
#=> NoMethodError (undefined method `each' for #<C:0x0000000002a118a8>)
class C
def each
end
end
c.reduce {}
#=> nil
This is why every class that includes Enumerable
must have an instance method each
that returns an enumerator and why each
is invoked on instances of that class before an instance method from Enumerable
is called.
Upvotes: 1
Reputation: 11542
The problem here, is that when you use each the variable inside the block, is the object inside the array not the index, so you can work as follow:
def high_card_points(hand)
sum = 0
hand.each do |card|
if card == "J"
sum += 1
elsif card == "Q"
sum += 2
elsif card == "K"
sum += 3
elsif card == "A"
sum += 4
end
end
sum
end
and if you execute in pry
[5] pry(main)* => :high_card_points
[6] pry(main)> high_card_points(cards)
=> 10
You can also work whit the index like with each_index
. But you can also take an other object-functional aproach:
You can create your class or monkeypatch the class string:
class String
def card_points
case self
when 'J'
1
when 'Q'
2
when 'K'
3
when 'A'
4
else
0
end
end
end
Then proceed like this:
[31] pry(main)> cards.map(&:card_points).inject(0, :+)
=> 10
Upvotes: 2