Скажіть кінець циклу .each у рубіні


90

Якщо у мене є цикл, такий як

users.each do |u|
  #some code
end

Де користувачі - це хеш декількох користувачів. Яку найпростішу умовну логіку можна побачити, якщо ви перебуваєте на останньому користувачі в хеші користувачів і хочете виконати лише певний код для цього останнього користувача, так щось

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end

Ви справді маєте на увазі хеш? Замовлення на хеш не завжди є надійним (залежно від того, як ви додаєте речі в хеш і яку рубінову версію ви використовуєте). При ненадійному впорядкуванні "останній" товар не буде узгодженим. Код у питанні та відповіді, які ви отримуєте, більше підходять для масиву або перелічуються. Наприклад, хеші не мають each_with_indexабо lastметоди.
Shadwell

1
Hashсуміші в Enumerable, так воно і є each_with_index. Навіть якщо хеш-клавіші не відсортовані, такий тип логіки з’являється весь час при рендерингу подань, де останній елемент може відображатися по-різному, незалежно від того, чи він насправді є «останнім» у будь-якому значущому для даних сенсі.
Raphomet

Звичайно, цілком правильно, у хешів є each_with_index, вибачення. Так, я бачу, що це може з’явитися; просто намагаюся прояснити питання. Особисто для мене найкращою відповіддю є використання, .lastале це не стосується лише хешу масиву.
Shadwell


1
@Andrew погоджується, що це абсолютно пов'язано, проте надзвичайна відповідь Meagars показує, що це не зовсім ошуканство.
Sam Saffron

Відповіді:


146
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end

4
Проблема з цим полягає в тому, що умовна умова запускається кожен раз. використовувати .lastза межами циклу.
bcackerman

3
Я просто додам, що якщо ви повторюєте хеш, ви повинні написати це так:users.each_with_index do |(key, value), index| #your code end
HUB

Я знаю, що це не зовсім одне і те ж питання, але цей код працює краще, я думаю, якщо ви хочете зробити що-небудь з кожним, АЛЕ останнім користувачем, тому я голосую за те, що саме це я і шукав
WhiteTiger

38

Якщо це така ситуація, коли ви застосовуєте певний код до всіх, крім останнього користувача, а потім лише до унікального коду до останнього користувача, одне з інших рішень може бути більш доречним.

Однак ви, здається, використовуєте однаковий код для всіх користувачів і деякий додатковий код для останнього користувача. Якщо це так, це здається більш правильним і чіткіше визначає ваш намір:

users.each do |u|
  #code for everyone
end

users.last.do_stuff() # code for last user

2
+1 за відсутність потреби в умовному, якщо це доречно. (звичайно, я тут був)
Джеремі

@meagar хіба цей цикл не пройде користувачів двічі?
мураха

@ant Ні, є один цикл і один дзвінок, .lastякий не має нічого спільного з циклічним циклом.
meagar

@meagar, тож для того, щоб дістатися до останнього, він внутрішньо не циклується до останнього елемента? він має спосіб безпосереднього доступу до елемента без циклу?
мураха

1
@ant Ні, в цьому немає жодного внутрішнього циклу .last. Колекція вже інстанційована, це просто простий доступ до масиву. Навіть якщо колекція ще не була завантажена (як, це все-таки було негідроване відношення ActiveRecord), lastвсе одно ніколи не цикли, щоб отримати останнє значення, це було б дико неефективно. Він просто модифікує запит SQL, щоб повернути останній запис. Тим не менш, ця колекція вже завантажена .each, тож тут не пов'язано більше складності, ніж якщо б ви це зробили x = [1,2,3]; x.last.
meagar

20

Я думаю, що найкращий підхід:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end

22
Проблема цієї відповіді полягає в тому, що якщо останній користувач також трапляється раніше у списку, умовний код буде викликаний кілька разів.
evanrmurphy

1
Ви повинні використовувати u.equal?(users.last), equal?метод порівнює object_id, а не значення об'єкта. Але це не спрацює із символами та цифрами.
Нафаа Бутефер


6
h = { :a => :aa, :b => :bb }
h.each_with_index do |(k,v), i|
  puts ' Put last element logic here' if i == h.size - 1
end

3

Іноді я вважаю, що краще розділити логіку на дві частини, одну для всіх користувачів та одну для останньої. Тому я зробив би щось подібне:

users[0...-1].each do |user|
  method_for_all_users user
end

method_for_all_users users.last
method_for_last_user users.last

3

Ви можете використовувати підхід @ meager також для ситуації або / або, коли ви застосовуєте якийсь код до всіх, крім останнього користувача, а потім якийсь унікальний код лише до останнього користувача.

users[0..-2].each do |u|
  #code for everyone except the last one, if the array size is 1 it gets never executed
end

users.last.do_stuff() # code for last user

Таким чином, вам не потрібен умовний!


@BeniBela Ні, [-1] - останній елемент. Немає [-0]. Отже, останнє - [-2].
coderuby

users[0..-2]правильно, як є users[0...-1]. Зверніть увагу на різні оператори діапазону ..проти ..., див. Stackoverflow.com/a/9690992/67834
Еліот Сайкс

2

Іншим рішенням є порятунок від StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end

4
Це не є гарним рішенням цієї проблеми. Не слід зловживати винятками для простого регулювання потоку, а для виняткових ситуацій.
meagar

@meagar Це насправді майже цілком круто. Я погоджуюсь з тим, що невиправлені винятки або випадки , які потрібно передати вищому методу, повинні застосовуватися лише у виняткових ситуаціях. Однак це акуратний (і, мабуть, єдиний) спосіб отримати доступ до власних знань Рубі про те, де закінчується ітератор. Якщо є інший спосіб, який, звичайно, є кращим. Єдине справжнє питання, яке я б вирішив із цим, полягає в тому, що воно, здається, пропускає і нічого не робить для першого елемента. Фіксація, яка перевела б її за межу незграбності.
Адамантиш

2
@meagar Вибачте за "аргумент від авторитету", але Мац не погоджується з вами. Насправді, StopIterationрозроблений з точної причини обробки виходів циклу. З книги Маца: "Це може здатися незвичним - виняток висувається для очікуваного стану припинення, а не для несподіваної та виняткової події. ( StopIterationЄ нащадком StandardErrorі IndexError; зауважте, що це один з єдиних класів винятків, у якому немає слова "Помилка" в його назві.) Рубі слідкує за Python у цій зовнішній техніці ітерації. (Докладніше ...)
BobRodes

(...) Розглядаючи завершення циклу як виняток, це робить вашу цикл циклу надзвичайно простим; немає необхідності перевіряти повернене значення nextдля спеціального значення кінця ітерації, і немає необхідності викликати якийсь next?предикат перед викликом next. "
BobRodes

1
@Adamantish Слід зазначити, що loop doмає імпліцит rescueпри зустрічі з a StopIteration; він спеціально використовується при зовнішній ітерації Enumeratorоб'єкта. loop do; my_enum.next; endбуде повторювати my_enumі виходити в кінці; немає необхідності вводити rescue StopIterationтуди. (Вам потрібно, якщо ви використовуєте whileабо until.)
BobRodes

0

Не існує останнього методу хешування для деяких версій ruby

h = { :a => :aa, :b => :bb }
last_key = h.keys.last
h.each do |k,v|
    puts "Put last key #{k} and last value #{v}" if last_key == k
end

Це відповідь, яка покращує чужу відповідь? Будь ласка, опублікуйте, у якій відповіді згадується "останній" метод і що ви пропонуєте для подолання цієї проблеми.
Artemix

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