Sagen Sie das Ende einer .each-Schleife in Rubin

87

Wenn ich eine Schleife wie habe

users.each do |u|
  #some code
end

Wobei Benutzer ein Hash mehrerer Benutzer ist. Was ist die einfachste bedingte Logik, um zu sehen, ob Sie sich auf dem letzten Benutzer im Benutzer-Hash befinden und nur bestimmten Code für diesen letzten Benutzer ausführen möchten, also so etwas wie

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end
Splashlin
quelle
Meinst du wirklich einen Hash? Die Bestellung eines Hashs ist nicht immer zuverlässig (abhängig davon, wie Sie dem Hash Dinge hinzufügen und welche Ruby-Version Sie verwenden). Bei unzuverlässiger Bestellung ist der 'letzte' Artikel nicht konsistent. Der Code in der Frage und die Antworten, die Sie erhalten, passen besser zu einem Array oder sind aufzählbar. Zum Beispiel haben Hashes keine each_with_indexoder lastMethoden.
Shadwell
1
Hashmischt in Enumerable, so hat es each_with_index. Selbst wenn die Hash-Schlüssel nicht sortiert sind, wird diese Art von Logik beim Rendern von Ansichten immer wieder angezeigt, wobei das letzte Element möglicherweise anders angezeigt wird, unabhängig davon, ob es in einem datenrelevanten Sinne tatsächlich "zuletzt" ist.
Raphomet
Natürlich, ganz richtig, Hashes haben each_with_index, Entschuldigung. Ja, ich kann sehen, dass es kommen würde; Ich versuche nur, die Frage zu klären. Persönlich ist die beste Antwort für mich die Verwendung von, .lastaber das gilt nicht für einen Hash, nur ein Array.
Shadwell
Duplikat des magischen ersten und letzten Indikators in einer Schleife in Ruby / Rails? Abgesehen davon ist dies eher ein Hash als ein Array.
Andrew Grimm
1
@ Andrew stimmt zu, dass es völlig verwandt ist, aber die großartige kleine Antwort von Meagars zeigt, dass es nicht gerade ein Betrüger ist.
Sam Saffron

Antworten:

144
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end
Raphomet
quelle
3
Das Problem dabei ist, dass jedes Mal eine Bedingung durchlaufen wird. Verwenden .lastaußerhalb der Schleife.
bcackerman
3
Ich möchte nur hinzufügen, dass, wenn Sie über einen Hash iterieren, Sie ihn wie users.each_with_index do |(key, value), index| #your code end
folgt
Ich weiß, dass es nicht ganz die gleiche Frage ist, aber dieser Code funktioniert besser. Ich denke, wenn Sie jedem etwas antun wollen, ABER dem letzten Benutzer, dann stimme ich zu, da ich danach gesucht habe
WhiteTiger
38

Wenn es sich um eine Entweder-Oder-Situation handelt, in der Sie Code auf alle außer dem letzten Benutzer und dann eindeutigen Code nur auf den letzten Benutzer anwenden, ist möglicherweise eine der anderen Lösungen besser geeignet.

Sie scheinen jedoch für alle Benutzer denselben Code und für den letzten Benutzer zusätzlichen Code auszuführen. Wenn dies der Fall ist, scheint dies korrekter zu sein und Ihre Absicht klarer auszudrücken:

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

users.last.do_stuff() # code for last user
meagar
quelle
2
+1, wenn keine Bedingung erforderlich ist, falls dies angemessen ist. (Natürlich habe ich hier)
Jeremy
@meagar Wird diese Schleife nicht zweimal durch Benutzer geführt?
Ameise
@ant Nein, es gibt eine Schleife und einen Aufruf, .lastder nichts mit Schleifen zu tun hat.
Meagar
@meagar Also, um zum letzten zu gelangen, wird es nicht intern bis zum letzten Element wiederholt? hat es eine Möglichkeit, direkt auf das Element zuzugreifen, ohne eine Schleife zu erstellen?
Ameise
1
@ant Nein, es ist keine interne Schleife beteiligt .last. Die Sammlung ist bereits instanziiert, es ist nur ein einfacher Zugriff auf ein Array. Selbst wenn die Sammlung noch nicht geladen worden wäre (wie in, es war immer noch eine nicht hydratisierte ActiveRecord-Beziehung), werden lastniemals Schleifen ausgeführt , um den letzten Wert zu erhalten. Dies wäre äußerst ineffizient. Es ändert einfach die SQL-Abfrage, um den letzten Datensatz zurückzugeben. Diese Sammlung wurde jedoch bereits von der geladen .each, sodass keine größere Komplexität erforderlich ist als bei Ihnen x = [1,2,3]; x.last.
Meagar
20

Ich denke, ein bester Ansatz ist:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
Alter Lagos
quelle
21
Das Problem bei dieser Antwort ist, dass der bedingte Code mehrmals aufgerufen wird, wenn der letzte Benutzer auch früher in der Liste auftritt.
evanrmurphy
1
Sie müssen verwenden u.equal?(users.last), die equal?Methode vergleicht die object_id, nicht den Wert des Objekts. Dies funktioniert jedoch nicht mit Symbolen und Zahlen.
Nafaa Boutefer
10

Hast du es versucht each_with_index?

users.each_with_index do |u, i|
  if users.size-1 == i
     #code for last items
  end
end
Teja Kantamneni
quelle
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
quelle
3

Manchmal finde ich es besser, die Logik in zwei Teile zu unterteilen, einen für alle Benutzer und einen für den letzten. Also würde ich so etwas machen:

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
quelle
3

Sie können den Ansatz von @ meager auch für eine Entweder-Oder-Situation verwenden, in der Sie Code auf alle außer dem letzten Benutzer und dann eindeutigen Code nur auf den letzten Benutzer anwenden.

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

Auf diese Weise brauchen Sie keine Bedingung!

coderuby
quelle
@ BeniBela Nein, [-1] ist das letzte Element. Es gibt kein [-0]. Der vorletzte ist also [-2].
Coderuby
users[0..-2]ist richtig, wie es ist users[0...-1]. Beachten Sie die verschiedenen Bereichsoperatoren ..vs ..., siehe stackoverflow.com/a/9690992/67834
Eliot Sykes
2

Eine andere Lösung ist die Rettung vor StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end
Ricardokrieg
quelle
4
Dies ist keine gute Lösung für dieses Problem. Ausnahmen sollten nicht für eine einfache Flusskontrolle missbraucht werden, sondern für Ausnahmesituationen .
Meagar
@meagar Das ist eigentlich fast ziemlich cool. Ich bin damit einverstanden, dass nicht gerettete Ausnahmen oder solche, die an eine höhere Methode weitergegeben werden müssen, nur für Ausnahmesituationen gelten sollten. Dies ist jedoch eine nette (und anscheinend einzige) Möglichkeit, Zugang zu Rubys nativem Wissen darüber zu erhalten, wo ein Iterator endet. Wenn es einen anderen Weg gibt, der natürlich bevorzugt wird. Das einzige wirkliche Problem, das ich damit angehen würde, ist, dass es scheinbar überspringt und nichts für das erste Element tut. Das zu beheben würde es über die Grenze der Unbeholfenheit bringen.
Adamantish
2
@meagar Entschuldigung für ein "Argument der Autorität", aber Matz ist anderer Meinung als Sie. In der Tat StopIterationist für den genauen Grund der Behandlung von Schleifenausgängen ausgelegt. Aus Matz 'Buch: "Dies mag ungewöhnlich erscheinen - eine Ausnahme wird für eine erwartete Beendigungsbedingung und nicht für ein unerwartetes und außergewöhnliches Ereignis ausgelöst. ( StopIterationIst ein Nachkomme von StandardErrorund IndexError; beachten Sie, dass es eine der wenigen Ausnahmeklassen ist, die das Wort nicht haben." "Fehler" in seinem Namen.) Ruby folgt Python in dieser externen Iterationstechnik. (Mehr ...)
BobRodes
(...) Indem Sie die Schleifenbeendigung als Ausnahme behandeln, wird Ihre Schleifenlogik extrem einfach. Es ist nicht erforderlich, den Rückgabewert von nextauf einen speziellen Wert für das Ende der Iteration zu überprüfen , und es ist nicht erforderlich, vor dem Aufruf ein next?Prädikat aufzurufen next. "
BobRodes
1
@Adamantish Es sollte beachtet werden, dass dies loop doimplizit ist, rescuewenn man auf a trifft StopIteration; Es wird speziell verwendet, wenn ein EnumeratorObjekt extern iteriert wird. loop do; my_enum.next; endwird iterieren my_enumund am Ende beenden; keine Notwendigkeit, dort ein zu setzen rescue StopIteration. (Sie müssen, wenn Sie whileoder verwenden until.)
BobRodes
0

Für einige Ruby-Versionen gibt es keine letzte Hash-Methode

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
quelle
Ist es eine Antwort, die die Antwort eines anderen verbessert? Bitte posten Sie, in welcher Antwort die letzte Methode erwähnt wird und was Sie vorschlagen, um dieses Problem zu lösen.
Artemix
Für die Versionen von Ruby, die keine haben last, ist der Schlüsselsatz ungeordnet, sodass diese Antwort sowieso falsche / zufällige Ergebnisse liefert.
Meagar