Dile el final de un bucle .each en ruby

91

Si tengo un bucle como

users.each do |u|
  #some code
end

Donde usuarios es un hash de varios usuarios. ¿Cuál es la lógica condicional más fácil para ver si está en el último usuario en el hash de usuarios y solo desea ejecutar un código específico para ese último usuario?

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end
Splashlin
fuente
¿De verdad te refieres a un hash? Hacer un pedido en un hash no siempre es confiable (dependiendo de cómo agregue cosas al hash y qué versión de ruby ​​esté usando). Con pedidos poco fiables, el "último" artículo no será coherente. El código de la pregunta y las respuestas que obtiene son más apropiados para una matriz o enumerable. Por ejemplo, los hash no tienen métodos each_with_indexo last.
Shadwell
1
Hashse mezcla en Enumerable, por lo que tiene each_with_index. Incluso si las claves hash no están ordenadas, este tipo de lógica surge todo el tiempo al representar vistas, donde el último elemento puede mostrarse de manera diferente independientemente de si es realmente "último" en algún sentido significativo para los datos.
Raphomet
Por supuesto, muy bien, los hashes tienen each_with_indexdisculpas. Sí, puedo ver que surgirá; solo tratando de aclarar la pregunta. Personalmente, la mejor respuesta para mí es el uso de, .lastpero eso no se aplica a un hash solo a una matriz.
Shadwell
¿Duplicado del primer y último indicador de Magic en un bucle en Ruby / Rails? , además de ser un hash en lugar de una matriz.
Andrew Grimm
1
@Andrew está de acuerdo en que está totalmente relacionado, sin embargo, la pequeña respuesta increíble muestra que no es exactamente un engaño.
Sam Saffron

Respuestas:

147
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end
Raphomet
fuente
4
El problema con esto es que se ejecuta un condicional cada vez. utilizar .lastfuera del bucle.
bcackerman
3
Solo agregaría que si está iterando sobre un hash, debe escribirlo como:users.each_with_index do |(key, value), index| #your code end
HUB
Sé que no es del todo la misma pregunta, pero este código funciona mejor. Creo que si quieres hacer algo con todos, PERO con el último usuario, estoy votando porque eso era lo que estaba buscando
WhiteTiger
38

Si se trata de una situación de una u otra, en la que está aplicando un código a todos menos al último usuario y luego un código único solo al último usuario, una de las otras soluciones podría ser más apropiada.

Sin embargo, parece que está ejecutando el mismo código para todos los usuarios y algún código adicional para el último usuario. Si ese es el caso, esto parece más correcto y establece más claramente su intención:

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

users.last.do_stuff() # code for last user
meagar
fuente
2
+1 por no necesitar un condicional, si es apropiado. (por supuesto, lo hice aquí)
Jeremy
@meagar, ¿no pasará esto dos veces por los usuarios?
Hormiga
@ant No, hay un bucle y una llamada .lastque no tiene nada que ver con el bucle.
meagar
@meagar así que para llegar al último, ¿no se repite internamente hasta el último elemento? ¿Tiene una forma de acceder al elemento directamente sin bucle?
hormiga
1
@ant No, no hay ningún bucle interno involucrado .last. La colección ya está instanciada, es solo un simple acceso a una matriz. Incluso si la colección aún no se había cargado (como en, todavía era una relación ActiveRecord no hidratada) lastnunca se repite para obtener el último valor, eso sería tremendamente ineficiente. Simplemente modifica la consulta SQL para devolver el último registro. Dicho esto, esta colección ya ha sido cargada por el .each, por lo que no hay más complejidad involucrada que si lo hiciera x = [1,2,3]; x.last.
meagar
20

Creo que el mejor enfoque es:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
Alter Lagos
fuente
22
El problema con esta respuesta es que si el último usuario también aparece antes en la lista, el código condicional se llamará varias veces.
evanrmurphy
1
tienes que usar u.equal?(users.last), el equal?método compara el object_id, no el valor del objeto. Pero esto no funcionará con símbolos y números.
Nafaa Boutefer
11

¿Lo intentaste each_with_index?

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

A veces me parece mejor separar la lógica en dos partes, una para todos los usuarios y otra para la última. Entonces haría algo como esto:

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

Puede usar el enfoque de @ meager también para una situación de una u otra, en la que está aplicando un código a todos menos al último usuario y luego un código único solo al último usuario.

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

¡De esta manera no necesitas un condicional!

codificador
fuente
@BeniBela No, [-1] es el último elemento. No hay [-0]. Entonces, el penúltimo es [-2].
coderuby
users[0..-2]es correcto, como está users[0...-1]. Tenga en cuenta los diferentes operadores de rango ..vs ..., consulte stackoverflow.com/a/9690992/67834
Eliot Sykes
2

Otra solución es rescatar de StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end
ricardokrieg
fuente
4
Ésta no es una buena solución a este problema. No se debe abusar de las excepciones para un control de flujo simple, son para situaciones excepcionales .
meagar
@meagar Esto es bastante bueno. Estoy de acuerdo en que las excepciones no rescatadas o las que deben pasarse a un método superior deben ser solo para situaciones excepcionales. Sin embargo, esta es una forma ordenada (y aparentemente única) de obtener acceso al conocimiento nativo de Ruby sobre dónde termina un iterador. Si hay otra forma que, por supuesto, es preferible. El único problema real que tomaría con esto es que parece omitir y no hacer nada para el primer elemento. Arreglar eso lo llevaría al límite de la torpeza.
Adamantish
2
@meagar Perdón por un "argumento de la autoridad", pero Matz no está de acuerdo contigo. De hecho, StopIterationestá diseñado por la razón precisa de manejar salidas de bucle. Del libro de Matz: "Esto puede parecer inusual: se genera una excepción para una condición de terminación esperada en lugar de un evento inesperado y excepcional. ( StopIterationEs un descendiente de StandardErrory IndexError; tenga en cuenta que es una de las únicas clases de excepción que no tiene la palabra "Error" en su nombre.) Ruby sigue a Python en esta técnica de iteración externa. (Más ...)
BobRodes
(...) Al tratar la terminación de bucle como una excepción, hace que su lógica de bucle sea extremadamente simple; no es necesario verificar el valor de retorno de nextpara un valor especial de fin de iteración, y no es necesario llamar a algún tipo de next?predicado antes de llamar next".
BobRodes
1
@Adamantish Cabe señalar que loop dotiene un implícito rescueal encontrar un StopIteration; se usa específicamente cuando se itera externamente un Enumeratorobjeto. loop do; my_enum.next; enditerará my_enumy saldrá al final; no es necesario poner un rescue StopIterationahí. (Tienes que hacerlo si usas whileo until.)
BobRodes
0

No existe un último método de hash para algunas versiones de 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
Sathianarayanan Sundaram
fuente
¿Es una respuesta que mejora la respuesta de otra persona? Por favor, publique qué respuesta menciona el método 'último' y qué propone para superar este problema.
Artemix
Para las versiones de Ruby que no tienen last, el conjunto de claves estará desordenado, por lo que esta respuesta devolverá resultados incorrectos / aleatorios de todos modos.
meagar