Tell the end of a .each loop in ruby

67,388

Solution 1

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

Solution 2

If it's an either/or situation, where you're applying some code to all but the last user and then some unique code to only the last user, one of the other solutions might be more appropriate.

However, you seem to be running the same code for all users, and some additional code for the last user. If that's the case, this seems more correct, and more clearly states your intent:

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

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

Solution 3

I think a best approach is:

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

Solution 4

Did you tried each_with_index?

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

Solution 5

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
Share:
67,388

Related videos on Youtube

Splashlin
Author by

Splashlin

Updated on April 14, 2022

Comments

  • Splashlin
    Splashlin about 2 years

    If i have a loop such as

    users.each do |u|
      #some code
    end
    

    Where users is a hash of multiple users. What's the easiest conditional logic to see if you are on the last user in the users hash and only want to execute specific code for that last user so something like

    users.each do |u|
      #code for everyone
      #conditional code for last user
        #code for the last user
      end
    end
    
    • Shadwell
      Shadwell over 13 years
      Do you really mean a hash? Ordering on a hash is not always reliable (depending on how you add things to the hash and what ruby version you are using). With unreliable ordering the 'last' item will not be consistent. The code in the question and the answers you are getting are more appropriate to an array or enumerable. For example, hashes don't have each_with_index or last methods.
    • Raphomet
      Raphomet over 13 years
      Hash mixes in Enumerable, so it does have each_with_index. Even if the hash keys aren't sorted, this sort of logic comes up all the time when rendering views, where the last item might be displayed differently regardless of whether it's actually "last" in any data-meaningful sense.
    • Shadwell
      Shadwell over 13 years
      Of course, quite right, hashes do have each_with_index, apologies. Yep, I can see that it would come up; just trying to clarify the question. Personally the best answer for me is the use of .last but that doesn't apply for a hash only an array.
    • oligan
      oligan over 13 years
      Duplicate of Magic First and Last Indicator in a Loop in Ruby/Rails?, apart from this being a hash rather than an array.
    • Sam Saffron
      Sam Saffron over 13 years
      @Andrew agree its totally related, however meagars awesome little answer shows how it is not exactly a dupe.
    • oligan
      oligan over 13 years
      @Sam: Could Meagar's answer be appropriate for the other question as well?
    • Sam Saffron
      Sam Saffron over 13 years
      @Andrew , I'm not sure I don't think so, the other question has no common clause for all the items in the collection
  • Jeremy
    Jeremy over 13 years
    +1 for not needing a conditional, if that's appropriate. (of course, I did here)
  • evanrmurphy
    evanrmurphy over 11 years
    The problem with this answer is that if the last user also occurs earlier in the list, then the conditional code will get called multiple times.
  • Artemix
    Artemix almost 11 years
    Is it an answer that improves someone else's answer? Please post which answer mentions 'last' method and what do you propose to overcome this issue.
  • bcackerman
    bcackerman over 10 years
    Problem with this is a conditional is ran through each time. use .last outside the loop.
  • user229044
    user229044 over 10 years
    This is not a good solution to this problem. Exceptions shouldn't be abused for simple flow control, they're for exceptional situations.
  • user229044
    user229044 over 10 years
    For the versions of Ruby which don't have a last, the set of keys will be unordered, so this answer will return wrong/random results anyways.
  • HUB
    HUB over 9 years
    I'd just add that if you're iterating over a hash, you have to write it like: users.each_with_index do |(key, value), index| #your code end
  • ant
    ant over 8 years
    @meagar won't this loop trough users twice?
  • user229044
    user229044 over 8 years
    @ant No, there is one loop and one call to .last which has nothing to do with looping.
  • ant
    ant over 8 years
    @meagar so in order to get to the last it doesn't internally loop until the last element? it has a way of accessing the element directly without looping?
  • user229044
    user229044 over 8 years
    @ant No, there is no internal looping involved in .last. The collection is already instantiated, it's just a simple accessing of an array. Even if the collection had not already been loaded (as in, it was still an unhydrated ActiveRecord relation) last still never loops to get the last value, that would be wildly inefficient. It simply modifies the SQL query to return the last record. That said, this collection has already been loaded by the .each, so there is no more complexity involved than if you did x = [1,2,3]; x.last.
  • ant
    ant over 8 years
    @meagar gotcha, thanks for the explanation, well said!
  • WhiteTiger
    WhiteTiger over 8 years
    I know it's not entirely the same question, but this code works better I think if you want to do something to everyone BUT the last user, so I'm upvoting since that was what I was looking for
  • Nafaa Boutefer
    Nafaa Boutefer over 8 years
    you have to use u.equal?(users.last), the equal? method compares the object_id, not the value of the object. But this will not work with symboles and numbers.
  • coderuby
    coderuby almost 8 years
    @BeniBela No, [-1] is the last element. There is no [-0]. So the second to last is [-2].
  • Kevin Walsh
    Kevin Walsh over 7 years
    users[0..-2] is correct, as is users[0...-1]. Note the different range operators .. vs ..., see stackoverflow.com/a/9690992/67834
  • Adamantish
    Adamantish over 7 years
    @ant I'm late to the party here but something that might be too obvious to @meagar to mention but wasn't always to me: Unlike an AR relation which will need to run an (efficient) database search to find the last item, a ruby array or hash already knows its own length. It has it pre-stored as an internal instance variable. This means it already knows exactly where to find the last item. In general Methods like count, last or find may do the same thing for different data structures / AR objects but work very differently underneath.
  • Adamantish
    Adamantish over 7 years
    @meagar This is actually nearly quite cool. I agree that unrescued exceptions or ones that need to be passed up to a higher method should only be for exceptional situations. This, however, is a neat (and apparently only) way of getting access to ruby's native knowledge of where an iterator ends. If there is another way that, is of course, preferred. The only real issue I'd take with this is that it appears to skip and do nothing for the first item. Fixing that would take it over the boundary of clunkiness.
  • BobRodes
    BobRodes over 4 years
    @meagar Sorry for an "argument from authority," but Matz disagrees with you. In fact, StopIteration is designed for the precise reason of handling loop exits. From Matz's book: "This may seem unusual—an exception is raised for an expected termination condition rather than an unexpected and exceptional event. (StopIteration is a descendant of StandardError and IndexError; note that it is one of the only exception classes that does not have the word “error” in its name.) Ruby follows Python in this external iteration technique. (more...)
  • BobRodes
    BobRodes over 4 years
    (...) By treating loop termination as an exception, it makes your looping logic extremely simple; there is no need to check the return value of next for a special end-of-iteration value, and there is no need to call some kind of next? predicate before calling next."
  • BobRodes
    BobRodes over 4 years
    @Adamantish It should be noted that loop do has an implicit rescue when encountering a StopIteration; it's specifically used when externally iterating an Enumerator object. loop do; my_enum.next; end will iterate my_enum and exit at the end; no need to put a rescue StopIteration in there. (You do have to if you use while or until.)
  • user229044
    user229044 over 4 years
    @BobRodes That's extremely interesting. Which book is that?
  • BobRodes
    BobRodes over 4 years
    @meagar I thought so too when I read it. It's one interesting book, and both readable and informative. Here's the Amazon link.