Indica la fine di un ciclo .each in ruby


91

Se ho un ciclo come

users.each do |u|
  #some code
end

Dove utenti è un hash di più utenti. Qual è la logica condizionale più semplice per vedere se sei sull'ultimo utente nell'hash degli utenti e vuoi eseguire solo codice specifico per quell'ultimo utente, quindi qualcosa di simile

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end

Intendi davvero un hashish? Ordinare su un hash non è sempre affidabile (dipende da come aggiungi elementi all'hash e dalla versione di ruby ​​che stai utilizzando). Con un ordine inaffidabile, l '"ultimo" articolo non sarà coerente. Il codice nella domanda e le risposte ottenute sono più appropriate per un array o enumerabili. Ad esempio, gli hash non hanno each_with_indexo lastmetodi.
Shadwell

1
Hashsi mescola in Enumerable, quindi ha each_with_index. Anche se le chiavi hash non sono ordinate, questo tipo di logica viene fuori tutto il tempo durante il rendering delle viste, dove l'ultimo elemento potrebbe essere visualizzato in modo diverso indipendentemente dal fatto che sia effettivamente "ultimo" in qualsiasi senso significativo per i dati.
Raphomet

Certo, è giusto, gli hash hanno each_with_index, scuse. Sì, posso vedere che sarebbe venuto fuori; sto solo cercando di chiarire la domanda. Personalmente la risposta migliore per me è l'uso di, .lastma questo non si applica a un hash solo un array.
Shadwell

Duplicato di Magic Primo e ultimo indicatore in un loop in Ruby / Rails? , a parte questo essere un hash piuttosto che un array.
Andrew Grimm

1
@Andrew concorda sul fatto che sia totalmente correlato, tuttavia la piccola risposta impressionante di Meagars mostra come non sia esattamente un duplicato.
Sam Saffron

Risposte:


147
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end

4
Il problema con questo è che un condizionale viene eseguito ogni volta. utilizzare .lastfuori dal ciclo.
bcackerman

3
Vorrei solo aggiungere che se stai iterando su un hash, devi scriverlo come:users.each_with_index do |(key, value), index| #your code end
HUB

So che non è del tutto la stessa domanda, ma questo codice funziona meglio, penso che se vuoi fare qualcosa a tutti MA l'ultimo utente, quindi sto votando perché era quello che stavo cercando
WhiteTiger

38

Se si tratta di una situazione o / o in cui stai applicando un codice a tutti tranne l'ultimo utente e quindi un codice univoco solo all'ultimo utente, una delle altre soluzioni potrebbe essere più appropriata.

Tuttavia, sembra che tu stia eseguendo lo stesso codice per tutti gli utenti e del codice aggiuntivo per l'ultimo utente. In tal caso, sembra più corretto e indica più chiaramente il tuo intento:

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

users.last.do_stuff() # code for last user

2
+1 per non aver bisogno di un condizionale, se appropriato. (ovviamente, l'ho fatto qui)
Jeremy

@meagar non questo loop attraverso gli utenti due volte?
formica

@ant No, c'è un loop e una chiamata .lastche non ha nulla a che fare con il loop.
meagar

@meagar quindi per arrivare all'ultimo non esegue un ciclo interno fino all'ultimo elemento? ha un modo per accedere direttamente all'elemento senza loop?
ant

1
@ant No, non è coinvolto alcun loop interno .last. La raccolta è già istanziata, è solo un semplice accesso a un array. Anche se la raccolta non fosse già stata caricata (come in, era ancora una relazione ActiveRecord non idratata) lastancora non si ripete mai per ottenere l'ultimo valore, ciò sarebbe estremamente inefficiente. Modifica semplicemente la query SQL per restituire l'ultimo record. Detto questo, questa raccolta è già stata caricata da .each, quindi non c'è più complessità che se lo facessi x = [1,2,3]; x.last.
meagar

20

Penso che l'approccio migliore sia:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end

22
Il problema con questa risposta è che se anche l'ultimo utente si trova prima nell'elenco, il codice condizionale verrà chiamato più volte.
evanrmurphy

1
devi usare u.equal?(users.last), il equal?metodo confronta object_id, non il valore dell'oggetto. Ma questo non funzionerà con simboli e numeri.
Nafaa Boutefer

11

Ci hai provato each_with_index?

users.each_with_index do |u, i|
  if users.size-1 == i
     #code for last items
  end
end

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

3

A volte trovo meglio separare la logica in due parti, una per tutti gli utenti e una per l'ultima. Quindi farei qualcosa del genere:

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

method_for_all_users users.last
method_for_last_user users.last

3

Puoi usare l'approccio di @ meager anche per una situazione o / o, in cui stai applicando un codice a tutti tranne l'ultimo utente e poi un codice univoco solo all'ultimo utente.

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

In questo modo non hai bisogno di un condizionale!


@BeniBela No, [-1] è l'ultimo elemento. Non c'è [-0]. Quindi il penultimo è [-2].
coderuby

users[0..-2]è corretto, così com'è users[0...-1]. Nota i diversi operatori di intervallo ..vs ..., vedi stackoverflow.com/a/9690992/67834
Eliot Sykes

2

Un'altra soluzione è salvare da StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end

4
Questa non è una buona soluzione a questo problema. Le eccezioni non dovrebbero essere abusate per un semplice controllo del flusso, sono per situazioni eccezionali .
meagar

@meagar Questo in realtà è abbastanza interessante. Sono d'accordo che le eccezioni non salvate o quelle che devono essere trasferite a un metodo superiore dovrebbero essere solo per situazioni eccezionali. Questo, tuttavia, è un modo pulito (e apparentemente unico) per ottenere l'accesso alla conoscenza nativa di Ruby di dove finisce un iteratore. Se c'è un altro modo, ovviamente, è preferito. L'unico vero problema che prenderei con questo è che sembra saltare e non fare nulla per il primo elemento. Riparare questo avrebbe superato il confine del rumore.
Adamantish

2
@meagar Scusa per un "argomento dell'autorità", ma Matz non è d'accordo con te. Infatti, StopIterationè progettato per il preciso motivo di gestire le uscite di loop. Dal libro di Matz: "Questo può sembrare insolito: viene sollevata un'eccezione per una condizione di terminazione attesa piuttosto che per un evento imprevisto ed eccezionale. ( StopIterationÈ un discendente di StandardErrore IndexError; nota che è una delle uniche classi di eccezione che non contiene la parola "Errore" nel nome.) Ruby segue Python in questa tecnica di iterazione esterna. (
Altro

(...) Trattando la terminazione del ciclo come un'eccezione, rende la logica del ciclo estremamente semplice; non è necessario controllare il valore di ritorno di nextper uno speciale valore di fine iterazione e non è necessario chiamare alcun tipo di next?predicato prima di chiamare next. "
BobRodes

1
@Adamantish Va notato che loop doha un implicito rescuequando si incontra un StopIteration; è usato specificamente quando si itera esternamente un Enumeratoroggetto. loop do; my_enum.next; enditererà my_enume uscirà alla fine; non c'è bisogno di metterne uno rescue StopIterationlì. (Devi farlo se usi whileo until.)
BobRodes

0

Non esiste un ultimo metodo per l'hash per alcune versioni di 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

È una risposta che migliora la risposta di qualcun altro? Pubblica la risposta che menziona il metodo "ultimo" e cosa proponi per superare questo problema.
Artemix

Per le versioni di Ruby che non hanno un last, il set di chiavi non sarà ordinato, quindi questa risposta restituirà comunque risultati errati / casuali.
meagar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.