Reputation: 17367
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
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
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
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
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
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
Reputation: 8380
Check out Jon Skeet's smart enumerations.
They are part of his Miscellaneous Utility Library
Upvotes: 1
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
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