"За" проти "кожного" в 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
Чи є вагома причина, чому x залишається в корпусі або це поганий дизайн: 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 знову в мережі.
pnomolos

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, оскільки змінна temp живе коротшим періодом.


1
Там поза невелика різниця (як yjerem, ChristopheD і Баярд згадки) щодо області видимості змінних.
Telemachus

Неправильно, forне використовується eachпід ним. Дивіться інші відповіді.
акун

@akuhn Для подальшого роз'яснення, будь ласка, дивіться це питання та обидва його чудові відповіді.
Сагар Пандія

2

Ніколи не використовуйте forйого, це може призвести до майже не відстежуваних помилок.

Не обманюйте, це не про ідіоматичний код або проблеми стилю. Реалізація Рубі 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. Насправді, в роботі працює з безліччю об'єктів так само, як кожен ітератор.

Колекція, передана в, може бути будь-яким об'єктом, який має метод кожного ітератора. Масиви та хеші визначають кожен метод, як і багато інших об’єктів Ruby. Цикл "в / в" викликає кожен метод зазначеного об'єкта. Оскільки цей ітератор дає значення, цикл 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

Як бачимо, обидва реагують на кожен метод, який повертає значення до блоку. Як усі тут заявили, безумовно, бажано використовувати кожен ітератор над циклом for 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

У циклі "для" локальна змінна все ще живе після кожного циклу. У циклі "кожен" локальна змінна оновлюється після кожного циклу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.