Identifying last loop when using for each

53,842

Solution 1

How about obtaining a reference to the last item first and then use it for comparison inside the foreach loop? I am not say that you should do this as I myself would use the index based loop as mentioned by KlauseMeier. And sorry I don't know Ruby so the following sample is in C#! Hope u dont mind :-)

string lastItem = list[list.Count - 1];
foreach (string item in list) {
    if (item != lastItem)
        Console.WriteLine("Looping: " + item);
    else    Console.Writeline("Lastone: " + item);
}

I revised the following code to compare by reference not value (can only use reference types not value types). the following code should support multiple objects containing same string (but not same string object) since MattChurcy's example did not specify that the strings must be distinct and I used LINQ Last method instead of calculating the index.

string lastItem = list.Last();
foreach (string item in list) {
    if (!object.ReferenceEquals(item, lastItem))
        Console.WriteLine("Looping: " + item);
    else        Console.WriteLine("Lastone: " + item);
}

Limitations of the above code. (1) It can only work for strings or reference types not value types. (2) Same object can only appear once in the list. You can have different objects containing the same content. Literal strings cannot be used repeatedly since C# does not create a unique object for strings that have the same content.

And i no stupid. I know an index based loop is the one to use. I already said so when i first posted the initial answer. I provided the best answer I can in the context of the question. I am too tired to keep explaining this so can you all just vote to delete my answer. I'll be so happy if this one goes away. thanks

Solution 2

I see a lot of complex, hardly readable code here... why not keep it simple:

var count = list.Length;

foreach(var item in list)
    if (--count > 0)
        Console.WriteLine("Looping: " + item);
    else
        Console.Writeline("Lastone: " + item);

It's only one extra statement!

Another common situation is that you want to do something extra or less with the last item, like putting a separator between the items:

var count = list.Length;

foreach(var item in list)
{
    Console.Write(item);
    if (--count > 0)
        Console.Write(",");
}

Solution 3

The foreach construct (in Java definitely, probably also in other languages) is intended to represent the most general kind if iteration, which includes iteration over collections that have no meaningful iteration order. For example, a hash-based set does not have an ordering, and therefore there is no "last element". The last iteration may yield a different element each time you iterate.

Basically: no, the foreach construct is not meant to be used that way.

Solution 4

Is this elegant enough? It assumes a non-empty list.

  list[0,list.length-1].each{|i|
    puts "Looping:"+i # if not last loop iteration
  }
  puts "Last one:" + list[list.length-1]

Solution 5

In Ruby I'd use each_with_index in this situation

list = ['A','B','C']
last = list.length-1
list.each_with_index{|i,index|
  if index == last 
    puts "Last one: "+i 
  else 
    puts "Looping: "+i # if not last loop iteration
  end
}
Share:
53,842
Matt
Author by

Matt

Updated on December 24, 2020

Comments

  • Matt
    Matt over 3 years

    I want to do something different with the last loop iteration when performing 'foreach' on an object. I'm using Ruby but the same goes for C#, Java etc.

      list = ['A','B','C']
      list.each{|i|
        puts "Looping: "+i # if not last loop iteration
        puts "Last one: "+i # if last loop iteration
      }
    

    The output desired is equivalent to:

      Looping: 'A'
      Looping: 'B'
      Last one: 'C'
    

    The obvious workaround is to migrate the code to a for loop using 'for i in 1..list.length', but the for each solution feels more graceful. What is the most graceful way to code a special case during a loop? Can it be done with foreach?

  • Matt
    Matt almost 15 years
    Oh never mind, you did say 'for' loop. Is a shame though, that I can't get the current position in my list, while in a foreach loop.
  • Matt
    Matt almost 15 years
    Somebody obviously objected, But I like this suggestion, it is thinking outside the box and would certainly do the job.
  • Lazarus
    Lazarus almost 15 years
    If the position in a list is important to you then an indexed for loop is the elegant solution, just because it's common and often misused doesn't make it inelegant.
  • Matt
    Matt almost 15 years
    That's another out the box idea. I like this and wouldn't have thought of it myself. This could actually work quite well for my problem. It would also allow special cases elsewhere in the loop.
  • Thomas Levesque
    Thomas Levesque almost 15 years
    What's wrong with that answer ? Voting down with no explanation really is useless...
  • Thomas Levesque
    Thomas Levesque almost 15 years
    In many cases you can't use index based iteration, because not all types of collections support it. For instance, take the IEnumerable interface (.NET) : you can enumerate the items, but not access them by index... you can't even know how many items there are in the enumeration. So foreach is the only way to go in that case...
  • Matt
    Matt almost 15 years
    Again this is different from other answers, which is great. I like diverse answers. However I'm not certain this would print all the items in the list.
  • LukeH
    LukeH almost 15 years
    @MattChurchy, @TomatoGG: It will print all non-null items in the list, but it falls down if the list contains any nulls.
  • Matt
    Matt almost 15 years
    I'm still not sure what this would do without compiling. But thank you for your answer. I think perhaps that the difficulty to interpret that the result would be rules it out as a elegant solution.
  • Svante
    Svante almost 15 years
    I think that this reluctance is not warranted. I guess it comes from the notion that methods "belong" to a class, as opposed to generic functions. I think that if you want new behaviour for Arrays, you have to do what it takes.
  • Kirschstein
    Kirschstein almost 15 years
    C# 3.0 will also have the .Last operator on the array
  • Matt
    Matt almost 15 years
    Tested it, does work. I'm finally with you. The 'previous' variable isn't a flag but holds the result from the previous loop. Pushing the last variable in the list outside the loop. - Thanks again for you answer
  • Matt
    Matt almost 15 years
    Thanks this seems to be a tighter syntax for TomatoGG's answer
  • Matt
    Matt almost 15 years
    Good stuff, if id known it was there I'd probably of used it a few times by now. Cheers
  • LukeH
    LukeH almost 15 years
    @Kirchstein: If the collection doesn't implement IList<T> then calling the Last method will involve evaluating the entire sequence so performance could be poor. If the collection does implement IList<T> then you can just do something like "var lastItem = list[list.Count - 1]" as TomatoGG does above.
  • annakata
    annakata almost 15 years
    +1 baffles me that the .Length/.Count answers above have had so many votes given this
  • LukeH
    LukeH almost 15 years
    @TomatoGG: If the list items can be accessed directly by index then why not just use a standard "for" loop?
  • bohdan_trotsenko
    bohdan_trotsenko almost 15 years
    this is a bug-prone code. You actually compare by value, not by index. If there is an object which equals to the last one, you'll have several "lastone: " lines.
  • klew
    klew almost 15 years
    You should add "end" after last puts, and this solution didn't work if you have list = ['A', 'B', 'C', 'A'] - it will print "Last one: A" two times.
  • Damarawy
    Damarawy almost 15 years
    Nice. You could also use list[0...-1] and list[-1] instead of list[0, list.length-1] and list[list.length-1] respectively.
  • molf
    molf almost 15 years
    This would be even better if the condition inside the block could be avoided. If you make eachwithlast take two lambdas, one for items up to n-1 and one for item n, it would be a little more friendly, I think.
  • Ksempac
    Ksempac almost 15 years
    Doing something different on last element doesn't mean you want a specific element to be the last. For example, if you have a List of String that you needs to concatenate into one big String with separators. You will loop on each String, concatenate the String, then concatenate your separator. On the last String, you don't want the separator to be added. So even though i agree that doing something specific for the last element is breaking a bit the logic behind "foreach", the fact that the last element isn't always the same is completly irrelevant.
  • NikolaDjokic
    NikolaDjokic almost 15 years
    Although the foreach statement only requires the Iterator pattern and not a specific order, it doesn't mean that it isn't useful for ordered collections. In fact it is better from the classic for with index loop since it saves you from out of bounds errors.
  • Thomas Levesque
    Thomas Levesque almost 15 years
    @Luke : you're right, I was assuming a list of non-null items... it would probably be better to have a 'firstPass' variable in addition to the 'previous' variable
  • Thomas Levesque
    Thomas Levesque almost 15 years
    Very elegant, I like it ! However I would prefer 'while(!last)' rather than the explicit break... And 'last' needs to be initialized
  • annakata
    annakata almost 15 years
    @Ksempac - fair enough, I felt guilty about writing that anyway :)
  • anonymous
    anonymous almost 15 years
    @luke becoz MattChurchy didn't say he cannot access using a for loop, he just wants to use foreach because it was more "graceful". I was simply answering his question as best I can. Since I do not know how to access the last item in Ruby, I did my best to approx. with C#. Like I said I prefer to use an index based loop myself if I have to write such code.
  • anonymous
    anonymous almost 15 years
    @modosanreves, I wasnt planning to compare by value. In my first paragraph, I mention "obtaining a reference". So I assume that ppl would understand that I meant to compare references, not values. I am new to stackoverflow so i though code was only for illustration. If I knew you guys gonna dissect my code line by line I guess I have to spend more time writing it in the future to be as clear as possible. So for anyone looking at the code, please replace item != lastItem to !item.ReferenceEquals(lastItem) ok? :-P – TomatoGG 0 secs ago
  • yfeldblum
    yfeldblum almost 15 years
    It's not more graceful to use a foreach loop than a for loop when what you actually need is a for loop. And you need a for loop because you need the current index in order to determine whether you are currently on the last index.
  • dotjoe
    dotjoe almost 15 years
    lol look at Animesh's other answer. completely off topic. maybe a bot?
  • Damarawy
    Damarawy almost 15 years
    Good idea, but you're comparing by value, so a list like ['A', 'B', 'C', 'D', 'C'] will get two last ones. Perhaps if you used !x.equal?(list.last)
  • anonymous
    anonymous almost 15 years
    Ya i know I need to compare references rather than values but I don't know how to do that in Ruby yet. So equal? is for comparing references?
  • anonymous
    anonymous almost 15 years
    wow this stackoverflow thing is really tough. don't really know how should i approach a question. should I try to provide a solution according to what the questioner wants or be an evangelist of standards and best practices by refusing to provide an answer that falls out of line?
  • LukeH
    LukeH almost 15 years
    @TomatoGG: Sorry, your edit is inferior to your original version - (1) It won't display "Last one" at all if the list contains value types. (2) Even when the list contains ref types, it still suffers from the same problem as your original if the same object appears several times in the collection. Try it with {"A","B","C","A","B","C"} and see what happens.
  • LukeH
    LukeH almost 15 years
    @TomatoGG: For this to work correctly then you need to find the last item by index, not by value and not by reference. That's why an elegant solution isn't really available using "foreach" if the collection isn't accessible by index, and if the collection is accessible by index then a plain "for" loop will provide a more elegant solution than "foreach".
  • Matt
    Matt almost 15 years
    I like the conciseness, I do happen to have a real world use where I know the array values are unique. (This is actually for test code rather then production use.) So for me this does the great. Though it doesn't strictly need collecting and printing separately.
  • Matt
    Matt almost 15 years
    Thanks I wasn't aware of this method and with definitely use in future.
  • Jay
    Jay almost 15 years
    The fact that a construct does not ALWAYS possess a certain property tells us nothing about whether or not it contains that property in the particular case of interest to us right now. If the original poster was intending to write a generic piece of code that would accept any iterable set and perform this operation, perhaps that would be a bad idea. But if he's talking about doing it in one particular program that he is writing, where he knows additional characteristics of the set, there's nothing wrong with using that knowledge.
  • oligan
    oligan about 14 years
    Thanks for your answer Svante and molf. Slightly more idiomatic ruby code might use each_with_index, and ruby usually uses snake_case for variables and method names. Like the shamelessly ripping off code here: stackoverflow.com/questions/2241684/…
  • Saurabh
    Saurabh about 14 years
    This is the first elegant solution I've seen that works on Iterable s that are not List s. +1.
  • David Burrows
    David Burrows about 13 years
    Ruby 1.9 hashes do indeed have an ordering, they preserve insertion order
  • zanbri
    zanbri over 12 years
    Great idea. But this method can't be applied to hashes, can it?
  • Arcolye
    Arcolye over 12 years
    Is there a name for these kind of "Repudiation without Solution" types of answers? I'm not a fan of them. David Burrows has the right answer.
  • DanielG
    DanielG about 11 years
    This is such a nice solution. Very fast comparison, and only one length lookup! I will be using this for a while now.
  • ghilesZ
    ghilesZ about 7 years
    This is my favourite (in the spirit) : it has the nice advantage of not doing the unnecessary if check at every iteration.
  • Patrik Lindström
    Patrik Lindström almost 7 years
    Nice solution I used it in C# where there is start and an end like array in json: var count = list.Count; Write("[") foreach (var item in list) { Console.Write(item); Write(--count > 0 ? "," : "]"); }
  • xpt
    xpt over 6 years
    This is a nice and elegant solution that works for all cases besides string type and should be chosen as the best approach. @Matt I'll upvote your question if you do that.