Перечислители в Ruby

У меня проблемы с пониманием Enumerators в Ruby.

Пожалуйста, поправьте меня. Если я ошибаюсь, метод o.enum_for(:arg) должен преобразовывать объект в Enumerator, и каждая итерация по объекту o должна вызывать метод arg? Что меня смущает, так это то, как работает эта строка кода.

[4, 1, 2, 0].enum_for(:count).each_with_index do |elem, index|
  elem == index
end

Он должен подсчитать, сколько элементов равно их положению в массиве, и это работает. Однако я не понимаю, что происходит на самом деле. Вызывает ли each_with_index метод подсчета на каждой итерации? Если бы кто-нибудь объяснил, было бы здорово.


person Zed    schedule 12.11.2012    source источник
comment
На самом деле нет, блок выполняется 4 раза, просто проверьте это в irb   -  person Zed    schedule 12.11.2012


Ответы (2)


Из программирования Ruby:

count enum.count {| объект | блок } → целое

Возвращает количество объектов в перечислении, которые равны obj или для которых блок возвращает истинное значение.

Таким образом, перечислитель посещает каждый элемент и добавляет 1 к счетчику, если блок возвращает значение true, что в данном случае означает, что элемент равен индексу массива.

Я не видел, чтобы этот паттерн использовался часто (если вообще использовался) — я был бы более склонен к следующему:

[4, 1, 2, 0].each_with_index.select { |elem, index| elem == index }.count

ИЗМЕНИТЬ

Давайте посмотрим на пример из вашего комментария:

[4, 1, 2, 0].enum_for(:each_slice, 2).map do |a, b|
  a + b
end

each_slice(2) принимает массив по 2 элемента за раз и возвращает массив для каждого среза:

[4, 1, 2, 0].each_slice(2).map # => [[4,1], [2,0]]

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

[4, 1, 2, 0].enum_for(:each_slice, 2).map do |a,b|
  puts "#{a.inspect} #{b.inspect}"
end

приводит к

4 1
2 0

a и b получают свои значения в силу того, что аргументы блока "разбрызгиваются":

a, b = *[4, 1]
a # => 4
b # => 1

Вместо этого вы также можете использовать срез массива в качестве аргумента:

[4, 1, 2, 0].enum_for(:each_slice, 2).map {|a| puts "#{a.inspect}"}

[4, 1]
[2, 0]

Что позволяет вам сделать это:

[4, 1, 2, 0].enum_for(:each_slice, 2).map {|a| a.inject(:+) } #=> [5,2]

Или, если у вас есть ActiveSupport (например, приложение Rails),

[4, 1, 2, 0].enum_for(:each_slice, 2).map {|a| a.sum }

Что кажется мне намного яснее, чем исходный пример.

person zetetic    schedule 12.11.2012
comment
Что меня действительно смущает, так это откуда |elemen, index| получить их значения. В моем первом примере это, я думаю, из each_with_index, но посмотрите на этот код [4, 1, 2, 0].enum_for(:each_slice, 2).map do |a, b| a + b конец, здесь, |a, b| получить от каждого_слайса... - person Zed; 13.11.2012

array.count обычно может принимать блок, но сам по себе возвращает фиксированный номер, поэтому его нельзя привязать к .with_index, как это делают некоторые другие итераторы (попробуйте array.map.with_index {|x,i ... } и т. д.).

.enum_for(:count) преобразует его в перечислитель, что позволяет выполнять эту цепочку. Он выполняет итерацию один раз по элементам массива и подсчитывает, сколько из них равны своим индексам. Таким образом, count действительно вызывается только один раз, но только после преобразования массива во что-то более гибкое.

person Zach Kemp    schedule 12.11.2012