«За» против «каждого» в Ruby

200

У меня просто был быстрый вопрос по поводу циклов в Ruby. Есть ли разница между этими двумя способами перебора коллекции?

# way 1
@collection.each do |item|
  # do whatever
end

# way 2
for item in @collection
  # do whatever
end

Просто интересно, если они точно такие же, или, может быть, есть небольшая разница (возможно, когда @collectionноль).

mportiz08
источник

Ответы:

315

Это единственное отличие:

каждый:

irb> [1,2,3].each { |x| }
  => [1, 2, 3]
irb> x
NameError: undefined local variable or method `x' for main:Object
    from (irb):2
    from :0

для:

irb> for x in [1,2,3]; end
  => [1, 2, 3]
irb> x
  => 3

С forциклом переменная итератора все еще живет после того, как блок сделан. С eachциклом это не так, если только он не был определен как локальная переменная до начала цикла.

Кроме этого, forэто просто синтаксический сахар для eachметода.

Когда @collectionесть nilобе петли бросить исключение:

Исключение: неопределенная локальная переменная или метод `@collection 'для main: Object

Джереми Рутен
источник
3
Есть ли веская причина, почему х остается в случае или это плохой дизайн: P? Мне кажется, это довольно неинтуитивно по сравнению с большинством других языков.
cyc115
3
@ cyc115 Причина, по которой xостается сценарий for, связана с тем, что (вообще говоря) ключевые слова не создают новых областей действия. Если , если , не начать , для , в то время и т. д. все работают с текущей областью. #eachоднако принимает блок. Блоки всегда добавляют свою собственную область поверх текущей области. Это означает, что объявление новой переменной в блоке (следовательно, новой области видимости) не будет доступно извне блока, поскольку эта дополнительная область там недоступна.
3limin4t0r
43

См. « Пороки For Loop » для хорошего объяснения (есть одно небольшое различие, учитывая переменную область видимости).

Использование eachв считаются более идиоматическим использованием Ruby.

ChristopheD
источник
@zachlatta: Спасибо за уведомление. Я отредактирую ссылку, чтобы указать на вариант статьи webarchive.org!
ChristopheD
1
graysoftinc.com/early-steps/the-evils-of-the-for-loop - это новая ссылка, теперь, когда сайт JEG2 вернулся в онлайн.
Пномолос
30

Ваш первый пример,

@collection.each do |item|
  # do whatever
end

более идиоматичный . Хотя Ruby поддерживает циклические конструкции, такие как forи while, обычно предпочтителен блочный синтаксис.

Другое тонкое отличие состоит в том, что любая переменная, которую вы объявляете в forцикле, будет доступна вне цикла, тогда как переменные в блоке итератора фактически являются закрытыми.

Баярд Рандель
источник
whileи на untilсамом деле есть некоторые очень конкретные применения, которые не могут быть заменены каждым, например, для генерации уникальных значений или для REPL.
более
6

Еще один другой ..

number = ["one", "two", "three"]
 => ["one", "two", "three"] 

loop1 = []
loop2 = []

number.each do |c|
  loop1 << Proc.new { puts c }
end
 => ["one", "two", "three"] 

for c in number
  loop2 << Proc.new { puts c }
end
 => ["one", "two", "three"] 

loop1[1].call
two
 => nil 

loop2[1].call
three
 => nil 

источник: http://paulphilippov.com/articles/enumerable-each-vs-for-loops-in-ruby

для более понятного: http://www.ruby-forum.com/topic/179264#784884

Мистер Блэк
источник
2

Похоже, что нет никакой разницы, forиспользует eachвнизу.

$ irb
>> for x in nil
>> puts x
>> end
NoMethodError: undefined method `each' for nil:NilClass
    from (irb):1
>> nil.each {|x| puts x}
NoMethodError: undefined method `each' for nil:NilClass
    from (irb):4

Как говорит Баярд, каждый более идиоматичен. Он скрывает от вас больше и не требует специальных языковых функций. Комментарий Телемаха

for .. in .. устанавливает итератор за пределы цикла, поэтому

for a in [1,2]
  puts a
end

листья aопределены после завершения цикла. Где как eachнет. Что является еще одной причиной в пользу использования each, потому что временная переменная живет более короткий период.

BaroqueBobcat
источник
1
Там вне небольшая разница (как yjerem, ChristopheD и Баярд упоминания) относительно области видимости переменных.
Телемах
Неправильно, forне использовать eachпод. Смотрите другие ответы.
'27 Акюна
@akuhn Для дальнейшего разъяснения смотрите этот вопрос и оба его отличных ответа.
Сагар Пандья
2

Никогда не используйте forего, это может привести к почти неразличимым ошибкам.

Не обманывайте себя, речь идет не о идиоматических проблемах с кодом или стилем. Реализация Ruby forимеет серьезный недостаток и не должна использоваться.

Вот пример, где forвводится ошибка,

class Library
  def initialize
    @ary = []
  end
  def method_with_block(&block)
    @ary << block
  end
  def method_that_uses_these_blocks
    @ary.map(&:call)
  end
end

lib = Library.new

for n in %w{foo bar quz}
  lib.method_with_block { n }
end

puts lib.method_that_uses_these_blocks

Печать

quz
quz
quz

Использование %w{foo bar quz}.each { |n| ... }отпечатков

foo
bar
quz

Зачем?

В forцикле переменная nопределяется один и только раз, а затем это одно определение используется для всех итераций. Следовательно, каждый блок ссылается на один и тот же, nкоторый имеет значение quzк моменту окончания цикла. Ошибка!

В eachцикле новая переменная nопределяется для каждой итерации, например, выше, переменная nопределяется три раза. Следовательно, каждый блок относится к отдельному nс правильными значениями.

akuhn
источник
0

Насколько я знаю, использование блоков вместо структур управления на языке более идиоматично.

Джонатан Стерлинг
источник
0

Я просто хочу особо отметить цикл for in в Ruby. Может показаться, что эта конструкция похожа на другие языки, но на самом деле это выражение, похожее на любую другую циклическую конструкцию в Ruby. Фактически, for работает с объектами Enumerable так же, как и каждый итератор.

Коллекция, передаваемая для in, может быть любым объектом, у которого есть метод каждого итератора. Массивы и хэши определяют каждый метод, также как и многие другие объекты Ruby. Цикл for / in вызывает каждый метод указанного объекта. Поскольку этот итератор выдает значения, цикл for присваивает каждое значение (или каждый набор значений) указанной переменной (или переменным) и затем выполняет код в теле.

Это глупый пример, но он иллюстрирует тот факт, что цикл for in работает с ЛЮБЫМ объектом, который имеет каждый метод, так же, как и каждый итератор:

class Apple
  TYPES = %w(red green yellow)
  def each
    yield TYPES.pop until TYPES.empty?
  end
end

a = Apple.new
for i in a do
  puts i
end
yellow
green
red
=> nil

А теперь каждый итератор:

a = Apple.new
a.each do |i|
  puts i
end
yellow
green
red
=> nil

Как вы можете видеть, оба отвечают на каждый метод, который возвращает значения обратно в блок. Как все здесь заявили, определенно предпочтительнее использовать каждый итератор, чем цикл in. Я просто хотел показать, что в цикле for нет ничего волшебного. Это выражение, которое вызывает каждый метод коллекции, а затем передает его в свой блок кода. Следовательно, это очень редкий случай, который вам нужно использовать для in. Используйте каждый итератор почти всегда (с дополнительным преимуществом области видимости блока).

Донато
источник
0
(1..4).each { |i| 


  a = 9 if i==3

  puts a 


}
#nil
#nil
#9
#nil

for i in 1..4

  a = 9 if i==3

  puts a

end
#nil
#nil
#9
#9

В цикле «for» локальная переменная все еще живет после каждого цикла. В «каждом» цикле локальная переменная обновляется после каждого цикла.

Кельвин Тан
источник