Diga o final de um .cada loop em rubi

91

Se eu tiver um loop como

users.each do |u|
  #some code
end

Onde usuários é um hash de vários usuários. Qual é a lógica condicional mais fácil para ver se você está no último usuário no hash de usuários e deseja apenas executar um código específico para esse último usuário, algo como

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end
Splashlin
fonte
Você realmente quer dizer um hash? Encomendar em um hash nem sempre é confiável (dependendo de como você adiciona coisas ao hash e qual versão de ruby ​​você está usando). Com pedidos não confiáveis, o 'último' item não será consistente. O código da pergunta e as respostas que você está obtendo são mais apropriados para uma matriz ou enumerável. Por exemplo, hashes não têm métodos each_with_indexou last.
Shadwell
1
Hashmistura em Enumerable, então tem each_with_index. Mesmo que as chaves hash não sejam classificadas, esse tipo de lógica surge o tempo todo ao renderizar visualizações, onde o último item pode ser exibido de forma diferente, independentemente de ser realmente o "último" em qualquer sentido significativo para dados.
Raphomet
Claro, muito certo, hashes tem each_with_index, desculpas. Sim, posso ver que surgiria; apenas tentando esclarecer a questão. Pessoalmente, a melhor resposta para mim é o uso de, .lastmas isso não se aplica a um hash, apenas a um array.
Shadwell
Duplicado do primeiro e último indicador de Magic em um loop em Ruby / Rails? , além de ser um hash em vez de uma matriz.
Andrew Grimm
1
@Andrew concorda que está totalmente relacionado, no entanto, uma pequena resposta incrível mostra como ele não é exatamente um idiota.
Sam Saffron,

Respostas:

148
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end
Raphomet
fonte
4
O problema com isso é que uma condicional é executada todas as vezes. usar .lastfora do loop.
bcackerman
3
Eu apenas acrescentaria que, se você estiver iterando sobre um hash, deverá escrever assim:users.each_with_index do |(key, value), index| #your code end
HUB
Eu sei que não é totalmente a mesma pergunta, mas este código funciona melhor, eu acho que se você quiser fazer algo para todos, MAS o último usuário, então estou votando positivamente, pois era isso que eu estava procurando
WhiteTiger
39

Se for uma situação ou / ou, em que você está aplicando algum código a todos, exceto o último usuário e, em seguida, algum código exclusivo apenas ao último usuário, uma das outras soluções pode ser mais apropriada.

No entanto, parece que você está executando o mesmo código para todos os usuários e algum código adicional para o último usuário. Se for esse o caso, isso parece mais correto e afirma com mais clareza sua intenção:

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

users.last.do_stuff() # code for last user
meagar
fonte
2
+1 por não precisar de uma condicional, se for apropriado. (claro, eu fiz aqui)
Jeremy
@meagar não vai fazer loop pelos usuários duas vezes?
Formiga de
@ant Não, há um loop e uma chamada .lastque não tem nada a ver com looping.
meagar
@meagar então para chegar ao último ele não faz um loop interno até o último elemento? tem uma maneira de acessar o elemento diretamente sem loop?
formiga de
1
@ant Não, não há loop interno envolvido em .last. A coleção já está instanciada, basta um simples acesso a um array. Mesmo se a coleção ainda não tivesse sido carregada (como em, ainda era uma relação ActiveRecord não hidratada), lastainda assim nunca faz loops para obter o último valor, isso seria extremamente ineficiente. Ele simplesmente modifica a consulta SQL para retornar o último registro. Dito isso, essa coleção já foi carregada pelo .each, portanto, não há mais complexidade envolvida do que se você carregasse x = [1,2,3]; x.last.
meagar
20

Acho que a melhor abordagem é:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
Alter Lagos
fonte
22
O problema com essa resposta é que se o último usuário também ocorrer no início da lista, o código condicional será chamado várias vezes.
evanrmurphy
1
você tem que usar u.equal?(users.last), o equal?método compara o object_id, não o valor do objeto. Mas isso não funcionará com símbolos e números.
Nafaa Boutefer
11

Você tentou each_with_index?

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

Às vezes acho melhor separar a lógica em duas partes, uma para todos os usuários e outra para a última. Então, eu faria algo assim:

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

Você pode usar a abordagem de @ meager também para uma situação ou / ou, em que aplica algum código a todos, exceto o último usuário e, em seguida, algum código exclusivo apenas ao último usuário.

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

Dessa forma, você não precisa de uma condicional!

coderuby
fonte
@BeniBela Não, [-1] é o último elemento. Não há [-0]. Portanto, o penúltimo é [-2].
coderuby
users[0..-2]está correto, como está users[0...-1]. Observe os diferentes operadores de alcance ..vs. ..., consulte stackoverflow.com/a/9690992/67834
Eliot Sykes
2

Outra solução é resgatar 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
fonte
5
Esta não é uma boa solução para este problema. As exceções não devem ser abusadas para controle de fluxo simples, elas são para situações excepcionais .
meagar
@meagar Isso é quase muito legal. Concordo que exceções não resgatadas ou aquelas que precisam ser passadas para um método superior devem ser apenas para situações excepcionais. Essa, no entanto, é uma maneira simples (e aparentemente única) de obter acesso ao conhecimento nativo de Ruby sobre onde termina um iterador. Se houver outra forma, é claro, preferida. O único problema real que eu teria com isso é que parece pular e não fazer nada para o primeiro item. Consertar isso o levaria além do limite do desajeitado.
Adamantish
2
@meagar Desculpe por um "argumento de autoridade", mas Matz discorda de você. Na verdade, ele StopIterationfoi projetado com a finalidade precisa de lidar com saídas de loop. Do livro de Matz: "Isso pode parecer incomum - uma exceção é gerada para uma condição de término esperada em vez de um evento inesperado e excepcional. ( StopIterationÉ um descendente de StandardErrore IndexError; observe que é uma das únicas classes de exceção que não tem a palavra “Erro” em seu nome.) Ruby segue Python nesta técnica de iteração externa. (Mais ...)
BobRodes
(...) Ao tratar o término do loop como uma exceção, torna sua lógica de loop extremamente simples; não há necessidade de verificar o valor de retorno de nextpara um valor especial de fim de iteração e não há necessidade de chamar algum tipo de next?predicado antes de chamar next. "
BobRodes
1
@Adamantish Deve-se notar que loop dotem um implícito rescueao encontrar um StopIteration; ele é usado especificamente ao iterar externamente um Enumeratorobjeto. loop do; my_enum.next; endirá iterar my_enume sair no final; não há necessidade de colocar um rescue StopIterationlá. (Você precisa se usar whileou until.)
BobRodes
0

Não há um último método para hash para algumas versões 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
fonte
É uma resposta que melhora a resposta de outra pessoa? Poste qual resposta menciona o método 'último' e o que você propõe para superar esse problema.
Artemix
Para as versões do Ruby que não possuem um last, o conjunto de chaves será desordenado, então esta resposta retornará resultados errados / aleatórios de qualquer maneira.
meagar