Podaj koniec pętli .each w rubinie

91

Jeśli mam pętlę, taką jak

users.each do |u|
  #some code
end

Gdzie użytkownicy to skrót wielu użytkowników. Jaka jest najłatwiejsza logika warunkowa, aby sprawdzić, czy jesteś na ostatnim użytkowniku w skrócie użytkowników i chcesz wykonać tylko określony kod dla tego ostatniego użytkownika, więc coś w rodzaju

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end
Splashlin
źródło
Czy naprawdę masz na myśli haszysz? Zamawianie za pomocą skrótu nie zawsze jest niezawodne (w zależności od tego, jak dodajesz elementy do skrótu i ​​jakiej wersji ruby ​​używasz). Przy niewiarygodnym zamówieniu „ostatnia” pozycja nie będzie spójna. Kod w pytaniu i odpowiedzi, które otrzymujesz, są bardziej odpowiednie dla tablicy lub wyliczalne. Na przykład skróty nie mają each_with_indexani lastmetod.
Shadwell,
1
Hashmiesza się w Enumerable, więc ma each_with_index. Nawet jeśli klucze haszujące nie są posortowane, ten rodzaj logiki pojawia się cały czas podczas renderowania widoków, gdzie ostatni element może być wyświetlany inaczej, niezależnie od tego, czy jest to faktycznie „ostatni” w jakimkolwiek sensie danych.
Raphomet
Oczywiście, mam rację each_with_index, przepraszam. Tak, widzę, że wyjdzie; próbuję tylko wyjaśnić pytanie. Osobiście najlepszą odpowiedzią dla mnie jest użycie, .lastale to nie dotyczy tylko tablicy.
Shadwell
Duplikat pierwszego i ostatniego wskaźnika Magic w pętli w Ruby / Rails? , poza tym, że jest to skrót, a nie tablica.
Andrew Grimm
1
@Andrew zgadza się, że jest to całkowicie powiązane, jednak męcząca, niesamowita mała odpowiedź pokazuje, że nie jest to do końca naśladowca.
Sam Saffron,

Odpowiedzi:

147
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end
Raphomet
źródło
4
Problem z tym polega na tym, że warunek jest wykonywany za każdym razem. używać .lastpoza pętlą.
bcackerman
3
Dodam tylko, że jeśli iterujesz po hashu, musisz napisać to tak:users.each_with_index do |(key, value), index| #your code end
HUB
Wiem, że to nie do końca to samo pytanie, ale ten kod działa lepiej. Myślę, że jeśli chcesz zrobić coś każdemu, ALE ostatni użytkownik, więc głosuję za głosem, ponieważ tego właśnie szukałem
WhiteTiger
38

Jeśli jest to sytuacja typu „albo / albo”, w której stosujesz kod do wszystkich użytkowników oprócz ostatniego, a następnie jakiś unikalny kod tylko do ostatniego użytkownika, jedno z pozostałych rozwiązań może być bardziej odpowiednie.

Wydaje się jednak, że uruchamiasz ten sam kod dla wszystkich użytkowników i dodatkowy kod dla ostatniego użytkownika. Jeśli tak jest, wydaje się to bardziej poprawne i jaśniej określa twój zamiar:

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

users.last.do_stuff() # code for last user
meagar
źródło
2
+1 za brak warunku, jeśli tak jest. (oczywiście zrobiłem tutaj)
Jeremy
@meagar czy ta pętla nie obejmie użytkowników dwukrotnie?
mrówka
@ant Nie, istnieje jedna pętla i jedno wywołanie, .lastktóre nie ma nic wspólnego z zapętleniem.
meagar
@meagar więc aby dostać się do ostatniego, nie zapętla się wewnętrznie aż do ostatniego elementu? ma sposób na dostęp do elementu bezpośrednio bez zapętlenia?
mrówka
1
@ant Nie, nie ma żadnego wewnętrznego zapętlenia .last. Kolekcja została już utworzona, to tylko prosty dostęp do tablicy. Nawet jeśli kolekcja nie została jeszcze załadowana (tak jak w przypadku, nadal była to nieuwodniona relacja ActiveRecord) lastnadal nigdy nie wykonuje pętli w celu uzyskania ostatniej wartości, byłoby to szalenie nieefektywne. Po prostu modyfikuje zapytanie SQL, aby zwrócić ostatni rekord. To powiedziawszy, ta kolekcja została już załadowana przez .each, więc nie ma większej złożoności niż gdybyś to zrobił x = [1,2,3]; x.last.
meagar
20

Myślę, że najlepszym podejściem jest:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
Alter Lagos
źródło
22
Problem z tą odpowiedzią polega na tym, że jeśli ostatni użytkownik występuje również wcześniej na liście, to kod warunkowy zostanie wywołany wiele razy.
evanrmurphy
1
musisz użyć u.equal?(users.last), equal?metoda porównuje object_id, a nie wartość obiektu. Ale to nie zadziała z symbolami i liczbami.
Nafaa Boutefer
11

Czy próbowałeś each_with_index?

users.each_with_index do |u, i|
  if users.size-1 == i
     #code for last items
  end
end
Teja Kantamneni
źródło
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
DigitalRoss
źródło
3

Czasami wydaje mi się, że lepiej rozdzielić logikę na dwie części, jedną dla wszystkich użytkowników i drugą dla ostatniej. Więc zrobiłbym coś takiego:

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

method_for_all_users users.last
method_for_last_user users.last
xlembouras
źródło
3

Możesz użyć podejścia @ meager również w sytuacji albo / albo, gdy stosujesz kod do wszystkich użytkowników oprócz ostatniego, a następnie unikalny kod tylko do ostatniego użytkownika.

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

W ten sposób nie potrzebujesz warunku!

coderuby
źródło
@BeniBela Nie, [-1] to ostatni element. Nie ma [-0]. Więc przedostatni to [-2].
coderuby
users[0..-2]jest poprawne, jak jest users[0...-1]. Zwróć uwagę na różne operatory zakresu w ..porównaniu z ..., patrz stackoverflow.com/a/9690992/67834
Eliot Sykes
2

Innym rozwiązaniem jest uratowanie przed StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end
ricardokrieg
źródło
4
To nie jest dobre rozwiązanie tego problemu. Wyjątki nie powinny być nadużywane do prostej kontroli przepływu, są one stosowane w wyjątkowych sytuacjach.
meagar
@meagar To jest właściwie prawie całkiem fajne. Zgadzam się, że nieuratowane wyjątki lub te, które należy przekazać do wyższej metody, powinny dotyczyć tylko wyjątkowych sytuacji. Jest to jednak zgrabny (i najwyraźniej jedyny) sposób na uzyskanie dostępu do natywnej wiedzy Rubiego o tym, gdzie kończy się iterator. Jeśli istnieje inny sposób, jest to oczywiście preferowane. Jedynym prawdziwym problemem, który podjąłbym z tym, jest to, że wydaje się, że przeskakuje i nic nie robi dla pierwszego elementu. Naprawienie tego przeniosłoby go poza granicę niezdarności.
Adamantish
2
@meagar Przepraszam za „argument z autorytetu”, ale Matz nie zgadza się z tobą. W rzeczywistości StopIterationjest zaprojektowany z konkretnego powodu obsługi wyjść pętli. Z książki Matza: „Może się to wydawać niezwykłe - wyjątek jest zgłaszany raczej dla oczekiwanego warunku zakończenia, a nie dla nieoczekiwanego i wyjątkowego zdarzenia. ( StopIterationJest potomkiem StandardErrori IndexError; zauważ, że jest to jedna z jedynych klas wyjątków, która nie zawiera słowa „Błąd” w nazwie.) Ruby podąża za Pythonem w tej zewnętrznej technice iteracji. (Więcej ...)
BobRodes
(...) Traktując zakończenie pętli jako wyjątek, sprawia, że ​​logika pętli jest niezwykle prosta; nie ma potrzeby sprawdzania wartości zwracanej nextprzez specjalną wartość końca iteracji i nie ma potrzeby wywoływania jakiegoś next?predykatu przed wywołaniem next. ”
BobRodes
1
@Adamantish Należy zauważyć, że loop doposiada ukryte rescueprzy spotkaniu z StopIteration; jest szczególnie używany podczas zewnętrznej iteracji Enumeratorobiektu. loop do; my_enum.next; enddokona iteracji my_enumi zakończy na końcu; nie ma potrzeby umieszczania rescue StopIterationtam. (Musisz, jeśli używasz whilelub until.)
BobRodes
0

W przypadku niektórych wersji języka Ruby nie ma ostatniej metody obliczania skrótu

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
Sathianarayanan Sundaram
źródło
Czy jest to odpowiedź, która poprawia odpowiedź kogoś innego? Napisz, która odpowiedź wspomina o „ostatniej” metodzie i co proponujesz, aby rozwiązać ten problem.
Artemix
Dla wersji Rubiego, które nie mają a last, zestaw kluczy będzie nieuporządkowany, więc ta odpowiedź i tak zwróci błędne / losowe wyniki.
meagar