13

Often I have to code a loop that needs a special case for the first item, the code never seems as clear as it should ideally be.

Short of a redesign of the C# language, what is the best way to code these loops?

// this is more code to read then I would like for such a common concept
// and it is to easy to forget to update "firstItem"
foreach (x in yyy)
{
  if (firstItem)
  {
     firstItem = false;
     // other code when first item
  }
  // normal processing code
}

// this code is even harder to understand
if (yyy.Length > 0)
{
   //Process first item;
   for (int i = 1; i < yyy.Length; i++)
   {  
      // process the other items.
   }
}
Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
  • 3
    I really don't see anything wrong with (your first example) checking a boolean, anyone who looks at it will quickly know what you are doing. – JD Isaacks Dec 08 '10 at 17:59

12 Answers12

13

How about:

using (var erator = enumerable.GetEnumerator())
{
    if (erator.MoveNext())
    {
        ProcessFirst(erator.Current);
        //ProcessOther(erator.Current); // Include if appropriate.

        while (erator.MoveNext())
            ProcessOther(erator.Current);
    }
}

You could turn that into an extension if you want:

public static void Do<T>(this IEnumerable<T> source, 
                         Action<T> firstItemAction,
                         Action<T> otherItemAction)
{
   // null-checks omitted

    using (var erator = source.GetEnumerator())
    {
        if (!erator.MoveNext())
            return;

        firstItemAction(erator.Current);

        while (erator.MoveNext())
           otherItemAction(erator.Current);            
    }
}
Ani
  • 111,048
  • 26
  • 262
  • 307
5

I'd be tempted to use a bit of linq

using System.Linq;

var theCollectionImWorkingOn = ...

var firstItem = theCollectionImWorkingOn.First();
firstItem.DoSomeWork();

foreach(var item in theCollectionImWorkingOn.Skip(1))
{
    item.DoSomeOtherWork();
}
ilivewithian
  • 19,476
  • 19
  • 103
  • 165
  • That really seems like overkill to avoid checking a `bool` flag in a loop. – Phil Hunt Dec 08 '10 at 17:16
  • 3
    The `firstItem` logic should be wrapped in a check to ensure that the collection has any elements... – jball Dec 08 '10 at 17:17
  • It's easy to read and it has a lower cyclomatic complexity. I'm not sure where the overkill is. – ilivewithian Dec 08 '10 at 17:19
  • the cyclomatic complexity would be the same for your example versus an `if` and flag inside a simple `foreach`, assuming the robustness of some sort of check around the `firstItem` logic. – jball Dec 08 '10 at 17:25
5

You could try:

collection.first(x=>
{
    //...
}).rest(x=>
{
    //...
}).run();

first / rest would look like:

FirstPart<T> first<T>(this IEnumerable<T> c, Action<T> a)
{
    return new FirstPart<T>(c, a);
}

FirstRest rest<T>(this FirstPart<T> fp, Action<T> a)
{
    return new FirstRest(fp.Collection, fp.Action, a);
}

You would need to define classed FirstPart and FirstRest. FirstRest would need a run method like so (Collection, FirstAction, and RestAction are properties):

void run()
{
    bool first = true;
    foreach (var x in Collection)
    {
        if (first) {
            FirstAction(x);
            first = false;
        }
        else {
             RestAction(x);
        }
    }
}
Scott Wisniewski
  • 24,561
  • 8
  • 60
  • 89
  • 2
    instead of first and rest you can use head and tail. to be more functional:) – Sergey Mirvoda Dec 08 '10 at 18:39
  • I chose this as the selected answer, as it can also solve http://stackoverflow.com/questions/3105613/best-loop-idiom-for-special-casing-the-last-element the a small extention. – Ian Ringrose Dec 09 '10 at 16:38
4

I use the first variable method all the time and it seems totally normal to me. If you like that better you can use LINQ First() and Skip(1)

var firstItem = yyy.First();
// do the whatever on first item

foreach (var y in yyy.Skip(1))
{
// process the rest of the collection
}
Ilia G
  • 10,043
  • 2
  • 40
  • 59
2

The way you wrote it is probably the cleanest way it can be written. After all, there is logic specific to the first element, so it has to be represented somehow.

Phil Hunt
  • 8,404
  • 1
  • 30
  • 25
2

In cases like this I would just use a for loop like this:

for(int i = 0;  i < yyy.Count; i++){
      if(i == 0){
          //special logic here
      }
}

Using a for loop also would allow you to do something special in other cases like on the last item, on even items in the sequence, ..etc.

Adrian
  • 2,911
  • 1
  • 17
  • 24
2

IMHO the most cleanest way is: try to avoid special cases for the first item. That may not work in every situation, of course, but "special cases" may indicate that your program logic is more complex than it needs to be.

By the way, I would not code

if (yyy.Length > 0)
{
   for(int i = 1; i <yyy.Length; i++)
   {  
      // ...
   }
}

but instead

   for(int i = 1; i <yyy.Length; i++)
   {  
      // ...
   }

(which is itself a simple example of how to avoid unnecessary dealing with a special case.)

Doc Brown
  • 19,739
  • 7
  • 52
  • 88
  • +1 for the "try to avoid special cases for the first item" sentence. – digEmAll Dec 08 '10 at 17:29
  • 1
    Did you mean this http://stackoverflow.com/questions/4390406/using-linq-or-otherwise-how-do-check-if-all-list-items-have-the-same-value-and-r ? If so, the best answers loop over your complete collection showing exactly what I said. – Doc Brown Dec 08 '10 at 20:08
1

Here's a slightly simpler extension method that does the job. This is a combination of KeithS's solution and my answer to a related Java question:

public static void ForEach<T>(this IEnumerable<T> elements,
                              Action<T> firstElementAction,
                              Action<T> standardAction)
{
    var currentAction = firstElementAction;
    foreach(T element in elements)
    {
        currentAction(element);
        currentAction = standardAction;
    }
}
Community
  • 1
  • 1
finnw
  • 47,861
  • 24
  • 143
  • 221
0

Both of those are perfectly acceptable algorithms for processing the first element differently, and there really isn't a different way to do it. If this pattern is repeated a lot, you could hide it behind an overload of ForEach():

public static void ForEach<T>(this IEnumerable<T> elements, Action<T> firstElementAction, Action<T> standardAction)
{
    var firstItem = true;
    foreach(T element in elements)
    {
        if(firstItem)
        {
            firstItem = false;
            firstElementAction(element)
        }
        else
            standardAction(element)
    }
}

...

//usage
yyy.ForEach(t=>(other code when first item), t=>(normal processing code));

Linq makes it a little cleaner:

PerformActionOnFirstElement(yyy.FirstOrDefault());
yyy.Skip(1).ForEach(x=>(normal processing code));
KeithS
  • 70,210
  • 21
  • 112
  • 164
0

Whilst I wouldn't personally do this, there is another way using enumerators, which alleviates the need for conditional logic. Something like this:

void Main()
{
    var numbers = Enumerable.Range(1, 5);
    IEnumerator num = numbers.GetEnumerator();

    num.MoveNext();
    ProcessFirstItem(num.Current); // First item

    while(num.MoveNext()) // Iterate rest
    {
        Console.WriteLine(num.Current);
    }

}

    void ProcessFirstItem(object first)
    {
        Console.WriteLine("First is: " + first);
    }

Sample output would be:

First is: 1
2
3
4
5
Dan Diplo
  • 25,076
  • 4
  • 67
  • 89
0

Another option I came up with is

enum ItemType
{
  First,
  Last,
  Normal
}

list.Foreach(T item, ItemType itemType) =>
{
   if (itemType == ItemType.First)
   {
   }

   // rest of code
};

Writing the extension method is left as an exercise for the reader… Also should two Boolean flags “IsFirst” and “IsLast” be used instead of ItemType enum, or ItemType be an object that has “IsFirst” and “IsLast” properties?

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
0

My solution:

foreach (var x in yyy.Select((o, i) => new { Object = o, Index = i } )
{
  if (x.Index == 0)
  {
    // First item logic
  }
  else
  {
    // Rest of items
  }
}
wensveen
  • 783
  • 10
  • 20