3

Say you want to loop through 3 elements like this:

for(int i=0; i<3; i++)
{
    doSomething();
}

Of course, this is the same as saying: doSomething(); doSomething(); doSomething();.

Now, let's say you want to do something BETWEEN each iteration, as if you are coding this: doSomething(); doBetween(); doSomething(); doBetween(); doSomething();

Notice how doSomething() is called 3 times, but doBetween() is called 2 times.

Right now, the only way I know how to do this in a loop is:

for(int i=0; i<3; i++)
{
    doSomething();
    if(i<2)
        doBetween();
}

To me, it seems inefficient to run that conditional within a loop. It also makes you have to look at it twice in order to understand the programming intention. Plus, if you change the "3" to something else in the "for" header, you could easily forget to change the conditional, especially as the logic grows. Not only that, but this trick won't work in a foreach loop because there is no easy way to detect whether we are running the last iteration.

What tips do you have for doing something like this in a way that gives you better performance, better readability, better maintainability, or in a foreach loop?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
hgarland
  • 33
  • 2
  • This has got me wondering if there's any programming language that has an explicit syntax for running a block of code between iterations of a loop like this. I guess that in a C-like language, it could be something like `for (whatever) { ... } between { ... }` Of course, `for` could just as well be `while` or `foreach`. (And there would be a dangling `between` problem, just like the dangling `else` problem....) – Stewart Nov 23 '16 at 12:20

7 Answers7

3
for(int i = 0; i < 3; i++) {
    if(i > 0) {
        doBetween();
    }
    doSomething();
}
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
3

What about something simple?

for(int i = 0; i < 3; i++) {
    doSomething();
    doBetween();
}
doSomething();
naveen
  • 53,448
  • 46
  • 161
  • 251
  • This would work for the code presented, but assuming he's iterating through a list (thus the foreach reference), this wouldn't work out so well. – Jaime Torres Sep 09 '12 at 22:22
  • 1
    The issue I see with this is that "doSomething" is repeat code. Even though it is a small repetition it is something that would need to be changed in 2 places if the call to doSomething changes. – MikeKulls Sep 09 '12 at 22:37
  • 1
    Also, as someone pointed out below you would need an extra check to make sure the list being iterated wasn't empty. Then there is the other issue of passing i (or the item being iterated) into doSomething – MikeKulls Sep 10 '12 at 00:15
  • @MikeKulls I would presume that, in the example, doSomething() is a placeholder for whatever code one wants to execute for each iteration of the loop, therefore it isn't necessarily a 'small'. But that reinforces your point about it being repeat code. – Stewart Nov 23 '16 at 11:54
1

Assuming you want to "doSomething" and "doBetween" on whatever you're iterating over in a foreach:

bool firstComplete = false;

foreach(Item i in ItemList)
{
   if (firstComplete)
   {
      doBewteen(i);
   }
   else
   {
      firstComplete = true;
   }

   doSomething(i);
}
Jaime Torres
  • 10,365
  • 1
  • 48
  • 56
  • @ThiefMaster looks fine to me – MikeKulls Sep 09 '12 at 22:36
  • @JTorres well it did look fine, now it looks overly verbose to me. – MikeKulls Sep 09 '12 at 23:18
  • @MikeKulls I thought it looked awesome too. The compiler disagreed with me. It seems ternary operator cannot be used on it's own. `Only assignment, call, increment, decrement, and new object expressions can be used as a statement`. The spirit lives on... but I'm a little more dead inside. – Jaime Torres Sep 09 '12 at 23:41
  • @JTorres I was wondering about that. When I first looked I thought it wouldn't work, then I presumed I was wrong. But I was wrong about being wrong which meant I was right to start with. So 2 wrongs can make a right I guess. – MikeKulls Sep 10 '12 at 00:12
  • @MikeKulls Indeed. Still dead inside though. – Jaime Torres Sep 10 '12 at 01:40
1

You could always write a reusable function, not sure this would be a good idea unless you needed to reuse this a lot

    public static IEnumerable<TSource> ForEach<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, Action<TSource> action, Action<TSource> between)
    {
        bool first = true;
        foreach (TSource item in source)
        {
            if (first) first = false; else between();
            action(item);
        }
        return source;
    }

This would be called like so:

myList.ForEach(i => DoSomething(), i => DoBetween());

Or

Enumerable.Range(0, 3).ForEach(i => DoSomething(), i => DoBetween());
MikeKulls
  • 2,979
  • 2
  • 25
  • 30
  • I like this answer the best. It's the only answer so far that makes the calling code easier to read, and less risky to maintain. And it enables foreach to be used. – hgarland Sep 10 '12 at 05:11
  • @hgarland Thanks :-) I find this ForEach function to be extremely useful in its simpler form, without the 'between' param. I use this a lot in linq. I also have an overload that passes in the index to the item being iterated which is often useful. – MikeKulls Sep 10 '12 at 23:53
1
{
    int i=0;

    if(i<3){
        while(true){
            doSomething();
            i++;
            if(i<3){
                doBetween();
            }
            else{
                break;
            }

        }
    }
}

Granted, it looks the most obscure BUT both statements are written only once and there is only one comparison per iteration. If the condition isn't met initially then nothing happens and it does also not use any helper variables. You only have to write the condition twice.

Mircode
  • 432
  • 5
  • 12
0

There isn't anything built in for that. You could do your own custom enumerator that would call something inbetween, but then it would be just as complicated just to call it.

No matter how you solve it, you can't get around doing a check for each iteration. A simple check like that is hardly going to hurt the performance.

I usually use a boolean flag for this:

bool first = true;
for (int i = 0; i < 3; i++) {
  if (first) {
    first = false;
  } else {
    doBetween();
  }
  doSomething();
}

You could also consider moving one iteration out of the loop:

doSomething();
for (int i = 1; i < 3; i++) {
  doBetween();
  doSomething();
}

However, if the loop could ever be empty (zero items), you would have to check for that first.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
0

This would allow you to define the start and endpoints before entering the loop...

int start = 0;
int limit = 3;
for (int i == start; i < limit; i++) {
    doSomething();
    if (i > start && i < limit - 1) {
        doBetween();
    }
}
Kevin
  • 552
  • 2
  • 4