Ruby: Как использовать .each для итерации по массиву? - PullRequest
0 голосов
/ 14 октября 2018

В мостике карточной игры четырем картам присваиваются значения очков: Джек: 1, Королева: 2, Король: 3, Туз: 4. При заданном массиве строк, соответствующих одной карточной руке (карты представлены следующим образом.: ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",«A»]), вернуть общее количество очков старшей карты за эту руку.

Я могу решить эту простую проблему с помощью цикла while, но я хотел бы узнать, как использовать .each для итерациии через массив. Вот мой код, который не работает

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

Теперь, когда я запускаю его, появляется ошибка no implicit conversion of String into Integer.Как мне сделать это правильно?

Ответы [ 2 ]

0 голосов
/ 14 октября 2018

В сообщении об ошибке указано «TypeError (без неявного преобразования строки в целочисленное значение)» и что исключение возникло в строке hand[i] == "J".Первый элемент, переданный в блок с помощью each и назначенный переменной блока i, равен i = hand.first #=> "2".Поэтому у нас есть hand["2"] == "J", или фактически hand.[]("2"), но метод Array # [] требует, чтобы его аргумент был целым числом, и «неявного преобразования строки в целое число не существует».

Позвольте мне теперь обратиться к другому аспекту вашего вопроса.

arr = ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]

Вы можете написать следующее.

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  

Я вас слышу.Вы говорите: «Я сказал, что хочу использовать .each!».Ну, у вас есть!Позвольте мне объяснить.

arr является экземпляром класса Array.Array имеет Module # include d the module Enumerable , поэтому мы можем вызвать метод экземпляра Enumerable # redu * on arr.(Array.included_modules #=> [Enumerable, Kernel]).

Как и все другие методы экземпляра в Enumerable, Enumerable # reduce (он же inject) требует приемника, который является экземпляром класса Перечислитель , но arr является экземпляром Array, а не Enumerator.Руби обходит это следующим образом.Когда reduce вызывается на arr, она видит, что arr не является экземпляром Enumerator, поэтому она проверяет, есть ли у arr метод each (то есть, arrкласс Array имеет метод экземпляра each).Это так, поэтому она вызывает each на arr для получения

enum = arr.each
  #=> #<Enumerator: ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J",
  #                  "Q", "K", "A"]:each>

Теперь у нас есть счетчик, для которого можно вызвать reduce:

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

Вы неArray#each не вызывается, но это, безусловно, так.Мы можем подтвердить это, включив Enumerable в класс, у которого нет метода each, и посмотрим, что произойдет.

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

Именно поэтому каждый класс, включающий Enumerable, должен иметь метод экземпляра.each, который возвращает перечислитель и почему each вызывается для экземпляров этого класса перед вызовом метода экземпляра из Enumerable.

0 голосов
/ 14 октября 2018

Проблема здесь в том, что когда вы используете каждую переменную внутри блока, это объект внутри массива, а не индекс, поэтому вы можете работать следующим образом:

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

и если вы выполняете вpry

[5] pry(main)* => :high_card_points
[6] pry(main)> high_card_points(cards)
=> 10

Вы также можете работать с индексом, как с each_index.Но вы также можете использовать другой объектно-функциональный подход:

Вы можете создать свой класс или установить обезьянью строку класса:

  class String
    def card_points
      case self
      when 'J'
        1
      when 'Q'
        2
      when 'K'
        3
      when 'A'
        4
      else
        0
      end
    end
  end

Затем действовать следующим образом:

[31] pry(main)> cards.map(&:card_points).inject(0, :+)
=> 10
...