Dites la fin d'une boucle .each en rubis

91

Si j'ai une boucle telle que

users.each do |u|
  #some code
end

Où utilisateurs est un hachage de plusieurs utilisateurs. Quelle est la logique conditionnelle la plus simple pour voir si vous êtes sur le dernier utilisateur dans le hachage des utilisateurs et que vous souhaitez uniquement exécuter du code spécifique pour ce dernier utilisateur, donc quelque chose comme

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end
Splashlin
la source
Voulez-vous vraiment dire un hasch? La commande sur un hachage n'est pas toujours fiable (selon la façon dont vous ajoutez des éléments au hachage et la version de ruby ​​que vous utilisez). Avec une commande peu fiable, le «dernier» article ne sera pas cohérent. Le code de la question et les réponses que vous obtenez sont plus appropriés pour un tableau ou un énumérable. Par exemple, les hachages n'ont each_with_indexni lastméthodes.
Shadwell
1
Hashse mélange dans Enumerable, donc c'est le cas each_with_index. Même si les clés de hachage ne sont pas triées, ce type de logique apparaît tout le temps lors du rendu des vues, où le dernier élément peut être affiché différemment, qu'il soit réellement "dernier" dans un sens significatif pour les données.
Raphomet
Bien sûr, tout à fait raison, les hachages ont des each_with_indexexcuses. Oui, je peux voir que cela arriverait; essayant juste de clarifier la question. Personnellement, la meilleure réponse pour moi est l'utilisation de .lastmais cela ne s'applique pas pour un hachage uniquement un tableau.
Shadwell
Dupliquer le premier et le dernier indicateur magique dans une boucle en Ruby / Rails? , à part cela étant un hachage plutôt qu'un tableau.
Andrew Grimm
1
@Andrew est d'accord que c'est totalement lié, mais ma petite réponse géniale montre comment ce n'est pas exactement une dupe.
Sam Saffron

Réponses:

147
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end
Raphomet
la source
4
Le problème avec ceci est qu'un conditionnel est exécuté à chaque fois. utiliser en .lastdehors de la boucle.
bcackerman
3
J'ajouterais simplement que si vous itérez sur un hachage, vous devez l'écrire comme:users.each_with_index do |(key, value), index| #your code end
HUB
Je sais que ce n'est pas tout à fait la même question, mais ce code fonctionne mieux, je pense que si vous voulez faire quelque chose à tout le monde MAIS le dernier utilisateur, donc je vote pour parce que c'était ce que je recherchais
WhiteTiger
38

S'il s'agit d'une situation soit / ou, où vous appliquez du code à tous sauf le dernier utilisateur, puis un code unique uniquement au dernier utilisateur, l'une des autres solutions pourrait être plus appropriée.

Cependant, vous semblez exécuter le même code pour tous les utilisateurs et du code supplémentaire pour le dernier utilisateur. Si tel est le cas, cela semble plus correct et indique plus clairement votre intention:

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

users.last.do_stuff() # code for last user
meagar
la source
2
+1 pour ne pas avoir besoin d'un conditionnel, si cela est approprié. (bien sûr, je l'ai fait ici)
Jeremy
@meagar cette boucle ne traversera-t-elle pas deux fois les utilisateurs?
fourmi
@ant Non, il y a une boucle et un appel .lastqui n'ont rien à voir avec la boucle.
meagar
@meagar donc pour arriver au dernier, il ne boucle pas en interne jusqu'au dernier élément? il a un moyen d'accéder directement à l'élément sans boucle?
fourmi
1
@ant Non, il n'y a pas de boucle interne impliquée .last. La collection est déjà instanciée, c'est juste un simple accès à un tableau. Même si la collection n'avait pas déjà été chargée (comme dans, c'était toujours une relation ActiveRecord non hydratée) lastne boucle toujours jamais pour obtenir la dernière valeur, ce serait extrêmement inefficace. Il modifie simplement la requête SQL pour renvoyer le dernier enregistrement. Cela dit, cette collection a déjà été chargée par le .each, il n'y a donc pas plus de complexité que si vous l'aviez fait x = [1,2,3]; x.last.
meagar
20

Je pense qu'une meilleure approche est:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
Alter Lagos
la source
22
Le problème avec cette réponse est que si le dernier utilisateur apparaît également plus tôt dans la liste, le code conditionnel sera appelé plusieurs fois.
evanrmurphy
1
vous devez utiliser u.equal?(users.last), la equal?méthode compare object_id, pas la valeur de l'objet. Mais cela ne fonctionnera pas avec les symboles et les nombres.
Nafaa Boutefer
11

Avez-vous essayé each_with_index?

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

Parfois, je trouve préférable de séparer la logique en deux parties, une pour tous les utilisateurs et une pour la dernière. Donc je ferais quelque chose comme ça:

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
la source
3

Vous pouvez également utiliser l'approche de @ maigre pour une situation soit / ou, où vous appliquez du code à tous sauf le dernier utilisateur, puis un code unique au dernier utilisateur.

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 cette façon, vous n'avez pas besoin d'un conditionnel!

coderuby
la source
@BeniBela Non, [-1] est le dernier élément. Il n'y a pas de [-0]. Donc, l'avant-dernier est [-2].
coderuby
users[0..-2]est correct, tel quel users[0...-1]. Notez les différents opérateurs de plage ..vs ..., voir stackoverflow.com/a/9690992/67834
Eliot Sykes
2

Une autre solution consiste à récupérer 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
la source
4
Ce n'est pas une bonne solution à ce problème. Les exceptions ne doivent pas être abusées pour un simple contrôle de flux, elles sont destinées à des situations exceptionnelles .
meagar
@meagar C'est en fait presque assez cool. Je conviens que les exceptions non résolues ou celles qui doivent être transmises à une méthode supérieure ne devraient concerner que des situations exceptionnelles. Ceci, cependant, est un moyen simple (et apparemment unique) d'accéder à la connaissance native de ruby ​​de l'endroit où un itérateur se termine. S'il y a un autre moyen qui, bien sûr, est préféré. Le seul vrai problème que je prendrais avec ceci est qu'il semble ignorer et ne rien faire pour le premier élément. Réparer cela le prendrait au-dessus de la limite de la maladresse.
Adamantish
2
@meagar Désolé pour un "argument de l'autorité", mais Matz n'est pas d'accord avec vous. En fait, StopIterationest conçu pour la raison précise de gérer les sorties de boucle. Extrait du livre de Matz: "Cela peut sembler inhabituel - une exception est levée pour une condition de terminaison attendue plutôt qu'un événement inattendu et exceptionnel. ( StopIterationEst un descendant de StandardErroret IndexError; notez que c'est l'une des seules classes d'exception qui n'a pas le mot «Erreur» dans son nom.) Ruby suit Python dans cette technique d'itération externe. (
Suite
(...) En traitant la terminaison de boucle comme une exception, cela rend votre logique de boucle extrêmement simple; il n'est pas nécessaire de vérifier la valeur de retour de nextpour une valeur de fin d'itération spéciale, et il n'est pas nécessaire d'appeler une sorte de next?prédicat avant d'appeler next. "
BobRodes
1
@Adamantish Il convient de noter qu'il loop doa un implicite rescuelors de la rencontre d'un StopIteration; il est spécifiquement utilisé lors de l'itération externe d'un Enumeratorobjet. loop do; my_enum.next; endva itérer my_enumet quitter à la fin; pas besoin de mettre un rescue StopIterationlà dedans. (Vous devez le faire si vous utilisez whileou until.)
BobRodes
0

Il n'y a pas de dernière méthode de hachage pour certaines versions 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
la source
Est-ce une réponse qui améliore la réponse de quelqu'un d'autre? Veuillez indiquer quelle réponse mentionne la «dernière» méthode et que proposez-vous pour résoudre ce problème.
Artemix
Pour les versions de Ruby qui n'ont pas de last, l'ensemble de clés ne sera pas ordonné, donc cette réponse renverra de toute façon des résultats erronés / aléatoires.
meagar