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


200

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

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

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

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

Ответы:


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.


@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, потому что временная переменная живет более короткий период.


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с правильными значениями.



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» локальная переменная все еще живет после каждого цикла. В «каждом» цикле локальная переменная обновляется после каждого цикла.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.