onof
onof

Reputation: 17367

Elegantly refactoring code like this (to avoid a flag)

I have a function running over an enumerable, but the function should be a little bit different for the first item, for example:

void start() { 
    List<string> a = ...
    a.ForEach(DoWork);
}

bool isFirst = true;

private void DoWork(string s) {
   // do something

   if(isFirst)
     isFirst = false;
   else
     print("first stuff");

   // do something
}

How would you refactor this to avoid that ugly flag?

Upvotes: 2

Views: 967

Answers (8)

Dr. Wily&#39;s Apprentice
Dr. Wily&#39;s Apprentice

Reputation: 10280

EDIT: added usage example, added a ForFirst method, reordered my paragraphs.

Below is a complete solution.

Usage is either of the following:

        list.ForFirst(DoWorkForFirst).ForRemainder(DoWork);
        // or 
        list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);

The crux is the ForNext method, which performs an action for the specified next set of items from the collection and returns the remaining items. I've also implemented a ForFirst method that simply calls ForNext with count: 1.

class Program
{
    static void Main(string[] args)
    {
        List<string> list = new List<string>();
        // ...

        list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
    }

    static void DoWorkForFirst(string s)
    {
        // do work for first item
    }

    static void DoWork(string s)
    {
        // do work for remaining items
    }
}

public static class EnumerableExtensions
{
    public static IEnumerable<T> ForFirst<T>(this IEnumerable<T> enumerable, Action<T> action)
    {
        return enumerable.ForNext(1, action);
    }

    public static IEnumerable<T> ForNext<T>(this IEnumerable<T> enumerable, int count, Action<T> action)
    {
        if (enumerable == null)
            throw new ArgumentNullException("enumerable");

        using (var enumerator = enumerable.GetEnumerator())
        {
            // perform the action for the first <count> items of the collection
            while (count > 0)
            {
                if (!enumerator.MoveNext())
                    throw new ArgumentOutOfRangeException(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Unexpected end of collection reached.  Expected {0} more items in the collection.", count));

                action(enumerator.Current);

                count--;
            }

            // return the remainder of the collection via an iterator
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }

    public static void ForRemainder<T>(this IEnumerable<T> enumerable, Action<T> action)
    {
        if (enumerable == null)
            throw new ArgumentNullException("enumerable");

        foreach (var item in enumerable)
        {
            action(item);
        }
    }
}

I felt a bit ridiculous making the ForRemainder method; I could swear that I was re-implementing a built-in function with that, but it wasn't coming to mind and I couldn't find an equivalent after glancing around a bit. UPDATE: After reading the other answers, I see there apparently isn't an equivalent built into Linq. I don't feel so bad now.

Upvotes: 0

TMN
TMN

Reputation: 3070

Depends on how you're "handling it differently". If you need to do something completely different, then I'd recommend handling the first element outside the loop. If you need to do something in addition to the regular element processing, then consider having a check for the result of the additional processing. It's probably easier to understand in code, so here's some:

string randomState = null; // My alma mater!
foreach(var ele in someEnumerable) {
    if(randomState == null) randomState = setState(ele);
    // handle additional processing here.
}

This way, your "flag" is really an external variable you (presumably) need anyway, so you're not creating a dedicated variable. You can also wrap it in an if/else if you don't want to process the first element like the rest of the enumeration.

Upvotes: 0

Andy_Vulhop
Andy_Vulhop

Reputation: 4789

It might seem rudimentary with all the shiny Linq stuff available, but there's always the old fashion for loop.

var yourList = new List<int>{1,1,2,3,5,8,13,21};
for(int i = 0; i < yourList.Count; i++)
{
    if(i == 0)
        DoFirstElementStuff(yourList[i]);
    else
        DoNonFirstElementStuff(yourList[i]);
}

This would be fine if you don't want to alter yourList inside the loop. Else, you'll probably need to use the iterator explicitly. At that point, you have to wonder if that's really worth it just to get rid of an IsFirst flag.

Upvotes: 0

Patrick
Patrick

Reputation: 991

It's hard to say what the "best" way to handle the first element differently is without knowing why it needs to be handled differently.

If you're feeding the elements of the sequence into the framework's ForEach method, you can't elegantly provide the Action delegate the information necessary for it to determine the element parameter's position in the source sequence, so I think an extra step is necessary. If you don't need to do anything with the sequence after you loop through it, you could always use a Queue (or Stack), pass the first element to whatever handler you're using through a Dequeue() (or Pop()) method call, and then you have the leftover "homogeneous" sequence.

Upvotes: 0

Andy_Vulhop
Andy_Vulhop

Reputation: 4789

It might be a bit heavy handed, but I pulled this from another SO question a while back.

public static void IterateWithSpecialFirst<T>(this IEnumerable<T> source,
    Action<T> firstAction,
    Action<T> subsequentActions)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        if (iterator.MoveNext())
        {
            firstAction(iterator.Current);
        }
        while (iterator.MoveNext())
        {
            subsequentActions(iterator.Current);
        }
    }
}

Upvotes: 2

Rune
Rune

Reputation: 8380

Check out Jon Skeet's smart enumerations.

They are part of his Miscellaneous Utility Library

Upvotes: 1

msarchet
msarchet

Reputation: 15242

Expounding on Jimmy Hoffa's answer if you actually want to do something with the first item you could do this.

DoFirstWork(a[0])

a.Skip(1).ForEach(DoWork)

If the point is that it is separate in logic from the rest of the list then you should use a separate function.

Upvotes: 9

abatishchev
abatishchev

Reputation: 100288

using System.Linq; // reference to System.Core.dll

List<string> list = ..
list.Skip(1).ForEach(DoWork) // if you use List<T>.ForEeach()

but I recommend you to write your one:

public static void ForEach(this IEnumerable<T> collection, Action<T> action)
{
    foreach(T item in collection)
        action(item);
}

So you could do just next:

list.Skip(1).ForEach(DoWork)

Upvotes: 0

Related Questions