Скажите конец каждого цикла в рубине

Если у меня есть цикл, например

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

person Splashlin    schedule 22.10.2010    source источник
comment
Вы действительно имеете в виду хэш? Заказ по хешу не всегда надежен (в зависимости от того, как вы добавляете элементы в хеш и какую версию Ruby вы используете). При ненадежном заказе «последний» товар не будет согласован. Код вопроса и получаемые ответы больше подходят для массива или перечислимого типа. Например, у хэшей нет методов each_with_index или last.   -  person Shadwell    schedule 23.10.2010
comment
Hash смешивается с Enumerable, поэтому у него есть each_with_index. Даже если хеш-ключи не отсортированы, такая логика возникает постоянно при рендеринге представлений, когда последний элемент может отображаться по-разному, независимо от того, действительно ли он последний в каком-либо значимом для данных смысле.   -  person Raphomet    schedule 23.10.2010
comment
Конечно, совершенно верно, у хэшей есть each_with_index, извиняюсь. Ага, я вижу, что это произойдет; просто пытаюсь прояснить вопрос. Лично для меня лучшим ответом является использование .last, но это не относится к хешу только для массива.   -  person Shadwell    schedule 23.10.2010
comment
Дубликат первого и последнего индикаторов Magic в цикле в Ruby / Rails?, кроме того, что это хеш, а не массив.   -  person Andrew Grimm    schedule 25.10.2010
comment
@Andrew согласен, что это полностью связано, однако потрясающий маленький ответ показывает, что это не совсем обман.   -  person Sam Saffron    schedule 25.10.2010
comment
@Sam: Может ли ответ Мигар быть подходящим и для другого вопроса?   -  person Andrew Grimm    schedule 25.10.2010
comment
@ Андрей, я не уверен, что так не думаю, в другом вопросе нет общего предложения для всех элементов коллекции   -  person Sam Saffron    schedule 25.10.2010


Ответы (8)


Если это ситуация "или-или", когда вы применяете некоторый код ко всем , кроме последнего пользователя, а затем некоторый уникальный код к только последнему пользователю, одному из других решения могут быть более подходящими.

Однако похоже, что вы используете один и тот же код для всех пользователей и некоторый дополнительный код для последнего пользователя. Если это так, это кажется более правильным и более четко выражает ваши намерения:

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

users.last.do_stuff() # code for last user
person meagar    schedule 22.10.2010
comment
+1 за то, что в условном выражении не нуждаются, если это уместно. (конечно, я здесь) - person Jeremy; 23.10.2010
comment
@meagar не будет ли эта петля проходить через пользователей дважды? - person ant; 16.09.2015
comment
@ant Нет, есть один цикл и один вызов .last, который не имеет ничего общего с циклом. - person meagar; 16.09.2015
comment
@meagar, значит, чтобы добраться до последнего, он не выполняет внутренний цикл до последнего элемента? у него есть способ доступа к элементу напрямую, без зацикливания? - person ant; 16.09.2015
comment
@ant Нет, в .last нет внутреннего цикла. Коллекция уже создана, это просто доступ к массиву. Даже если коллекция еще не была загружена (например, это все еще было неводное отношение ActiveRecord) last по-прежнему никогда не зацикливается для получения последнего значения, это было бы крайне неэффективно. Он просто изменяет запрос SQL, чтобы вернуть последнюю запись. Тем не менее, эта коллекция уже была загружена .each, поэтому здесь не сложнее, чем если бы вы сделали x = [1,2,3]; x.last. - person meagar; 16.09.2015
comment
@meagar gotcha, спасибо за объяснение, хорошо сказано! - person ant; 18.09.2015
comment
@ant Я опаздываю на вечеринку, но что-то, что может быть слишком очевидным для @meagar, чтобы упомянуть, но не всегда для меня: в отличие от отношения AR, для которого потребуется выполнить (эффективный) поиск в базе данных, чтобы найти последний элемент , массиву ruby ​​или хешу уже известна собственная длина. Он предварительно сохранен как внутренняя переменная экземпляра. Это означает, что он уже точно знает, где найти последний элемент. В общем, такие методы, как count, last или find, могут делать одно и то же для разных структур данных / объектов AR, но работают по-разному под ними. - person Adamantish; 19.01.2017

Я думаю, что лучший подход:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
person Alter Lagos    schedule 25.10.2012
comment
Проблема с этим ответом заключается в том, что если последний пользователь также встречается раньше в списке, то условный код будет вызываться несколько раз. - person evanrmurphy; 11.12.2012
comment
вы должны использовать u.equal?(users.last), метод equal? сравнивает object_id, а не значение объекта. Но с символами и числами это не сработает. - person Nafaa Boutefer; 30.01.2016

Вы пробовали each_with_index?

users.each_with_index do |u, i|
  if users.size-1 == i
     #code for last items
  end
end
person Teja Kantamneni    schedule 22.10.2010

Еще одно решение - спасти от StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end
person ricardokrieg    schedule 11.10.2013
comment
Это не лучшее решение этой проблемы. Не следует злоупотреблять исключениями для простого управления потоком, они предназначены для исключительных ситуаций. - person meagar; 15.01.2014
comment
@meagar Это на самом деле довольно круто. Я согласен с тем, что неотстраненные исключения или исключения, которые необходимо передать вышестоящему методу, должны применяться только в исключительных ситуациях. Однако это изящный (и, по-видимому, единственный) способ получить доступ к внутренним знаниям Ruby о том, где заканчивается итератор. Если есть другой способ, это, конечно, предпочтительнее. Единственная реальная проблема, с которой я бы столкнулся, это то, что он, кажется, пропускает и ничего не делает для первого элемента. Исправление этого перешло бы границы неуклюжести. - person Adamantish; 19.01.2017
comment
@meagar Извините за аргумент от авторитета, но Мац с вами не согласен. Фактически, StopIteration предназначен именно для обработки выходов из цикла. Из книги Матца: Это может показаться необычным - исключение возникает для ожидаемого условия завершения, а не для неожиданного и исключительного события. (StopIteration является потомком StandardError и IndexError; обратите внимание, что это один из немногих классов исключений, в названии которого нет слова «ошибка».) Ruby следует за Python в этой технике внешней итерации. (более...) - person BobRodes; 19.01.2020
comment
(...) Рассматривая завершение цикла как исключение, это делает вашу логику цикла чрезвычайно простой; нет необходимости проверять возвращаемое значение next на предмет специального значения конца итерации, и нет необходимости вызывать какой-либо предикат next? перед вызовом next. - person BobRodes; 19.01.2020
comment
@Adamantish Следует отметить, что loop do имеет неявное rescue при обнаружении StopIteration; он специально используется при внешней итерации объекта Enumerator. loop do; my_enum.next; end выполнит итерацию my_enum и завершится в конце; нет необходимости вставлять там rescue StopIteration. (Это необходимо, если вы используете while или until.) - person BobRodes; 19.01.2020
comment
@BobRodes Это очень интересно. Что это за книга? - person meagar; 19.01.2020
comment
@meagar Я тоже так думал, когда читал. Это одна интересная книга, одновременно удобочитаемая и информативная. Вот ссылка на Amazon. - person BobRodes; 19.01.2020

Иногда мне кажется лучше разделить логику на две части: одну для всех пользователей и одну для последней. Итак, я бы сделал что-то вроде этого:

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

method_for_all_users users.last
method_for_last_user users.last
person xlembouras    schedule 30.04.2014

Вы можете использовать подход @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

Таким образом, вам не понадобится условное выражение!

person coderuby    schedule 16.09.2014
comment
@BeniBela Нет, [-1] - последний элемент. Нет [-0]. Итак, предпоследний - [-2]. - person coderuby; 21.05.2016
comment
users[0..-2] правильно, как и users[0...-1]. Обратите внимание на разные операторы диапазона .. и ..., см. stackoverflow.com/a/9690992/67834 - person Eliot Sykes; 12.01.2017

Для некоторых версий 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
person Sathianarayanan Sundaram    schedule 23.07.2013
comment
Это ответ, который улучшает чей-то ответ? Пожалуйста, опубликуйте, в каком ответе упоминается «последний» метод и что вы предлагаете для решения этой проблемы. - person Artemix; 23.07.2013
comment
Для версий Ruby, у которых нет last, набор ключей будет неупорядоченным, поэтому этот ответ в любом случае вернет неправильные / случайные результаты. - person meagar; 15.01.2014

person    schedule
comment
Проблема с этим условием выполняется каждый раз. используйте .last вне цикла. - person bcackerman; 19.08.2013
comment
Я бы просто добавил, что если вы перебираете хеш, вы должны написать его так: users.each_with_index do |(key, value), index| #your code end - person HUB; 27.08.2014
comment
Я знаю, что это не совсем тот же вопрос, но этот код работает лучше, я думаю, если вы хотите что-то сделать для всех, НО с последним пользователем, поэтому я голосую, так как это было то, что я искал - person WhiteTiger; 12.12.2015