Нам даны два массива:
RANKS = (("2".."10").to_a + %w(Jack Queen King Ace)).freeze
#=> ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
SUITS = %w(Hearts Clubs Diamonds Spades).freeze
#=> ["Hearts", "Clubs", "Diamonds", "Spades"]
Наш первый шаг - вычислить произведение этих двух массивов:
arr = RANKS.product(SUITS)
#=> [["2", "Hearts"], ["2", "Clubs"], ["2", "Diamonds"], ["2", "Spades"],
# ["3", "Hearts"], ["3", "Clubs"], ["3", "Diamonds"], ["3", "Spades"],
# ...
# ["Ace", "Clubs"], ["Ace", "Diamonds"], ["Ace", "Spades"]]
Чтобы напечатать элементы элементов этого массивамы можем написать:
arr.map do |a|
rank = a[0]
suit = a[1]
p rank
p suit
end
"2"
"Hearts"
"2"
"Clubs"
"2"
...
"Ace"
"Spades"
Поскольку только один объект Ruby передается в блок за раз, наличие единственной переменной блока имеет смысл.Первый элемент arr
передается в блок, и переменной блока присваивается его значение:
a = arr.first
#=> ["2", "Hearts"]
Затем я использовал метод Array # [] (собственно, первыйформа этого метода показана в документе) для извлечения первого и второго элементов массива a
и присвоения этих значений переменным rank
и suit
.
Вторым способом, которым мы могли бы это сделатьвыглядит следующим образом.
arr.map do |a|
rank, suit = a
p rank
p suit
end
Используется Array # декомпозиция (поиск "Разложение массива").
Для удобства Ruby позволяет нам использовать декомпозицию массивадля непосредственного вычисления нескольких переменных блока:
arr.map do |rank, suit|
p rank
p suit
end
При передаче первого элемента arr
в блок Ruby выполняет следующее:
rank, suit = arr.first
#=> ["2", "Hearts"]
rank
#=> "2"
suit
#=> "Hearts"
Иногда переменные блока записываются вболее сложный способ.Предположим, например,
arr = [[1, [2, [3, {:a=>4}]], 5, 6]], [7, [8, [9, {:b=>10}]], 11, 12]]]
Мы могли бы написать следующее:
arr.each do |a,(b,(c,d),e,f)|
p a
p b
p c
p d
p e
p f
end
1
2
3
{:a=>4}
5
6
7
8
9
{:b=>10}
11
12
Это может показаться несколько сложным, но на самом деле это довольно просто.Вы поймете, что я имею в виду, если сравните расположение скобок, содержащихся в каждом элементе arr
, с расположением скобок, окружающих группы переменных блока.
Предположим, что теперь мы не интересовались значениямиb
, e
или f
.Затем мы могли бы написать:
arr.each do |a,(_,(c,d),*)|
p a
p c
p d
end
1
3
{:a=>4}
7
9
{:b=>10}
Важная причина записи блочных переменных, подобных этой, заключается в том, что она сообщает читателю, какие компоненты каждого элемента arr
используются в вычислениях блока.
Наконец, следующее типично для шаблона, который часто встречается.
arr = [1, 3, 4, 7, 2, 6, 9]
arr.each_with_object([]).with_index { |(n,a),i| a << n*n if i.odd? }
#=> [9, 49, 36]
Здесь мы имеем следующее:
enum0 = arr.each_with_object([])
#=> #<Enumerator: [1, 3, 4, 7, 2, 6, 9]:each_with_object([])>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator:
# [1, 3, 4, 7, 2, 6, 9]:each_with_object([])>:with_index>
Перечислитель enum1
генерирует каждыйзначение передает его в блок, значениям блока присваиваются значения и выполняется расчет блока.Вот что происходит с первыми тремя значениями, сгенерированными enum1
.
(n,a),i = enum1.next
#=> [[1, []], 0]
n #=> 1
a #=> []
i #=> 0
a << n*n if i.odd?
#=> nil
(n,a),i = enum1.next
#=> [[3, []], 1]
n #=> 3
a #=> []
i #=> 1
a << n*n if i.odd?
#=> [9]
(n,a),i = enum1.next
#=> [[4, [9]], 2]
n #=> 4
a #=> [9]
i #=> 2
a << n*n if i.odd?
#=> nil
a #=> [9]
См. Enumerable # each_with_object , Enumerator # with_index и Enumerator #следующий .