Как ведет себя Ruby Array # map, когда блок имеет два параметра? - PullRequest
0 голосов
/ 02 января 2019

У меня есть следующий код:

RANKS = ((2..10).to_a + %w(Jack Queen King Ace)).freeze
SUITS = %w(Hearts Clubs Diamonds Spades).freeze

RANKS.product(SUITS).map do |rank, suit|
  p rank
  p suit
end

Я заметил, что когда я запускал этот код, я получил значение, и костюм был напечатан, но когда я использую только 1 параметр, например, | rank | Я бы получил подмассив, например [2, "Hearts"].

Означает ли это, что когда блок имеет 2 параметра, он обращается к sub-array[0] и sub-array[1]?

Это действительно получилоЯ запутался, и любая помощь была бы очень признательна.

Ответы [ 2 ]

0 голосов
/ 02 января 2019

Семантика формальной привязки параметров для формальных параметров блока отличается от семантики привязки формальных параметров для формальных параметров метода.В частности, они гораздо более гибки в том, как они обрабатывают несоответствия между количеством формальных параметров и фактическими аргументами.

  • Если существует ровно один формальный параметр блока, и вы yield более одного фактического блокааргумент, формальный параметр блока привязывается к Array, содержащему фактические аргументы блока.
  • Если имеется более одного формального параметра блока, и вы yield точно один фактический аргумент блока и этот фактический аргументравен Array, тогда формальные параметры блока привязываются к отдельным элементам Array.
  • Если у вас yield больше фактических аргументов блока, чем у блока есть формальные параметры, дополнительные фактические аргументы получаютигнорируется.
  • Если вы передаете меньше фактических аргументов, чем блок имеет формальные параметры, тогда эти дополнительные формальные параметры определены, но не связаны, и оцениваются в nil (точно так же, как определенные, но унифицированные локальные переменные).

Если вы внимательно посмотрите, вы можете увидеть, чтоСемантика связывания формальных параметров для формальных параметров блока намного ближе к семантике присваивания, т. е. вы можете представить присваивание с формальными параметрами блока в левой части оператора присваивания и фактическими аргументами блока в правой части.

Если вы определили блок, подобный этому:

{|a, b, c|}

и yield вот так:

yield 1, 2, 3, 4

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

a, b, c = 1, 2, 3, 4

Интересный факт: до Ruby 1.8 блокировка формального параметра привязки была с использованием фактического назначения!Вы можете, например, определить константу, переменную экземпляра, переменную класса, глобальную переменную и даже средство записи атрибутов (!!!) в качестве формального параметра, и когда вы yield редактируете этот блок, Ruby будет буквально выполнить задание:

class Foo
  def bar=(value)
    puts "`#{__method__}` called with `#{value.inspect}`"
    @bar = value
  end

  attr_reader :bar
end

def set_foo
  yield 42
end

foo = Foo.new

set_foo {|foo.bar|}
# `bar=` called with `42`

foo.bar
#=> 42

Довольно безумно, да?

Наиболее широко используемое применение этой семантики связывания формальных параметров блоков - при использовании Hash#each(или любой из Enumerable методов с экземпляром Hash в качестве получателя).Hash#each метод yield - единственный двухэлементный Array, содержащий ключ и значение в качестве фактического аргумента блока, но мы почти всегда рассматриваем его так, как если бы он yield представлял ключ и значение как отдельныефактические аргументы.Обычно мы предпочитаем писать

hsh.each do |k, v|
  puts "The key is #{k} and the value is #{v}"
end

вместо

hsh.each do |key_value_pair|
  k, v = key_value_pair
  puts "The key is #{k} and the value is #{v}"
end
0 голосов
/ 02 января 2019

Нам даны два массива:

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 #следующий .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...