tags:

views:

1202

answers:

7

Since C# doesn't have a before,after,last,first etc. as part of its foreach. The challenge is to mimic this behavior as elegantly as possible with the following criteria:

  1. Must allow: before, first, even, odd, last, after events
  2. Give an option execute/not execute the primary function (function executed on all objects of the collection) during the events listed in #1

If you can exceed the above criteria, pleases do!

I'll post my answer below, but its not elegant nor is it feasible, so I would like to see what the community can conjure up.

hard coded for loops get annoying sometimes =(

A: 

My Code, not elegant.

    public enum forEachExeuction
    {
        Concurrent,
        Seperate
    }

    public delegate void forEachDelegate(object o);

    public static void forEach(
        IList collection,
        forEachDelegate function,
        forEachDelegate before,
        forEachDelegate first,
        forEachDelegate evens,
        forEachDelegate odds,
        forEachDelegate last,
        forEachDelegate after,
        forEachExeuction when)
    {
        bool doBefore = before != null;
        bool doFirst = first != null;
        bool doEvens = evens != null;
        bool doOdds = odds != null;
        bool doLast = last != null;
        bool doAfter = after != null;
        bool conCurrent = when == forEachExeuction.Concurrent;

        int collectionCount = collection.Count;

        for (int i = 0; i < collectionCount; i++)
        {
            if (doBefore && i == 0)
            {
                before(collection[i]);
            }

            if (doFirst && i == 0)
            {
                first(collection[i]);
                if (conCurrent)
                    function(collection[i]);
            }
            else if (doEvens && i % 2 != 0)
            {
                evens(collection[i]);
                if (conCurrent)
                    function(collection[i]);
            }
            else if (doOdds && i % 2 == 0)
            {
                odds(collection[i]);
                if (conCurrent)
                    function(collection[i]);
            }
            else if (doLast && i == collectionCount - 1)
            {
                last(collection[i]);
                if (conCurrent)
                    function(collection[i]);
            }
            else
            {
                function(collection[i]);
            }

            if (after != null && i == collectionCount - 1)
            {
                after(collection[i]);
            }
        }
    }

Simple call to it:

    string[] testCollection = {"1", "2", "3", "4", "5"};

    forEachDelegate primaryFunction = delegate(object o)
    {
        Response.Write(o.ToString());
    };

    forEachDelegate first = delegate(object o)
    {
        Response.Write("first");
    };

    forEachDelegate odd = delegate(object o)
    {
        Response.Write("odd");
    };

    forEachDelegate after = delegate(object o)
    {
        Response.Write("after");
    };

    Randoms.forEach(testCollection, primaryFunction, null, first, null, odd, null, after, Randoms.forEachExeuction.Concurrent);

You would need to cast each object in each delegate to the proper object. Default parameters and named parameters would make this look much better. /wait for C# 4.0

Baddie
A: 

By supporting all your requirements in one very flexible method, you end up with the coding equivalent of a Swiss Army knife.

Better to provide all those capabilities as separate components that you can compose to suit each situation.

Linq provides a great starting point. Think of foreach as a way of converting a list of objects into a list of actions. Take that literally: a sequence of Action delegates, or IEnumerable<Action>.

To execute a sequence of actions conveniently, you need:

public static void Execute(this IEnumerable<Action> actions)
{
    foreach (var a in actions)
        a();
}

Now the problem is simply to provide sequence manipulation (much of which is already in Linq) to let you put together a list of actions according to your requirements, so you can put Execute on the end.

Daniel Earwicker
+10  A: 

LINQ...

  • after: .SkipWhile(predicate) (left vague as your meaning isn't clear)
  • before: .TakeWhile(predicate) (left vague as your meaning isn't clear)
  • last: .Last()
  • first: .First()
  • odd: .Where((x,i)=>i%2==1)
  • even: .Where((x,i)=>i%2==0)
Marc Gravell
+2  A: 

Jon Skeet wrote SmartEnumerable for this purpose. It's easily extended to provide IsOdd and IsEven properties.

Marc Gravell's answer is good because it's simple, but it will be less performant for cases when you first want to do something on all the odd elements and then on all the even elements, since that would have you iterate over all elements twice, while only a single time is strictly necessary.

I suppose you could write a function from an element to some enumeration and group all elements by the enumeration value they map to. Then you can easily handle each group seperately. But I'm not sure how this would perform, as I'm not sure what LINQ grouping specifically does and how much it's deferred.

Joren
Just an aside - for that scenario "Push LINQ" might fit - it allows multiple separate processing stacks (still using the same LINQ syntax), so you can have **exactly** my two `Where`s (actually, all of them), but only iterate the data once.
Marc Gravell
Ah yes, a push model would definitely be the best solution for cases like that.
Joren
+4  A: 
public class NavigationItem<T>
{
    readonly T _value;
    readonly bool _isFirst, _isLast, _isEven;

    internal NavigationItem(T value, bool isFirst, bool isLast, bool isEven)
    {
        _value = value;
        _isFirst = isFirst;
        _isLast = isLast;
    }

    public T Value { get { return _value; } }
    public bool IsFirst { get { return _isFirst; } }
    public bool IsLast { get { return _isLast; } }
    public bool IsEven { get { return _isEven; } }
    public bool IsOdd { get { return !_isEven; } }
}

public static class CollectionNavigation
{
    public IEnumerable<NavigationItem<T>> ToNavigable<T>(this IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");
        return ToNavigableCore(collection);
    }

    private IEnumerable<NavigationItem<T>> ToNavigableCore<T>(IEnumerable<T> collection)
    {
        using(var lEnumerator = collection.GetEnumerator())
        {
            if (lEnumerator.MoveNext())
            {
                T lCurrent = lEnumerator.Current;
                bool lIsFirst = true, lIsEven = true;

                while(lEnumerator.MoveNext())
                {
                    yield return new NavigationItem<T>(lCurrent, lIsFirst, false, lIsEven);
                    lIsFirst = false;
                    lIsEven = !lIsEven;
                    lCurrent = lEnumerator.Current;
                }

                yield return new NavigationItem<T>(lCurrent, lIsFirst, true, lIsEven);
            }
        }
    }
}

Using it:

foreach(var item in collection.ToNavigable())
{
    if (item.IsFirst) { ... }
    if (item.IsLast) { ... }
    if (item.IsEven) { ... }
    if (item.IsOdd) { ... }

    Console.WriteLine(item.Value);
}
Tommy Carlier
A: 

It should of course be generic so that you don't have to cast the values. You can use the Action delegate in the framwork instead of declaring your own.

public enum ForEachExecuction {
  Concurrent,
  Seperate
}

public static class ForEach<T> {

  private static bool Call(Action<T> function, bool condition, T value, Action<T> concurrent) {
    condition &= function != null;
    if (condition) {
      function(value);
      if (concurrent != null) concurrent(value);
    }
    return condition;
  }

  public static void Loop(
    IList<T> collection,
    Action<T> function,
    Action<T> before,
    Action<T> first,
    Action<T> evens,
    Action<T> odds,
    Action<T> last,
    Action<T> after,
    ForEachExecuction when)
  {
    Action<T> concurrent = when == ForEachExecuction.Concurrent?function:null;
    for (int i = 0; i < collection.Count; i++) {
      T value = collection[i];
      Call(before, i == 0, value, null);
      if (!Call(first, i == 0, value, concurrent)) {
        if (!Call(evens, i % 2 != 0, value, concurrent)) {
          if (!Call(odds, i % 2 == 0, value, concurrent)) {
            if (!Call(last, i==collection.Count-1, value, concurrent)) {
              function(value);
            }
          }
        }
      }
      Call(after, i == collection.Count - 1, value, null);
    }
  }

}

Calling:

string[] testCollection = { "1", "2", "3", "4", "5" };

Action<string> primaryFunction = delegate(string s) { Console.WriteLine(s); }
Action<string> first = delegate(string s) { Console.WriteLine("first"); }
Action<string> odd = delegate(string s) { Console.WriteLine("odd"); }
Action<string> after = delegate(string s) { Console.WriteLine("after"); }

ForEach<string>.Loop(testCollection, primaryFunction, null, first, null, odd, null, after, ForEachExecuction.Concurrent);

Or perhaps:

ForEach<string>.Loop(testCollection,
  (s) => Console.WriteLine(s),
  null,
  (s) => Console.WriteLine("first"),
  null,
  (s) => Console.WriteLine("odd"),
  null,
  (s) => Console.WriteLine("after"),
  ForEachExecuction.Concurrent
);
Guffa
A: 
for (;;) {}

Seriously.

*P.S.: Conditions like Odd and After rely on strict and meaningful order, which makes foreach wrong in both meaning and syntax. One needs to know when to stop following fashion. Notice how suggested LINQ .SkipWhile(predicate) differs semantically from After(index) *

ima
How so? `foreach` executes the statement *for each* item in the collection, in the exact order they appear in the collection. It has the idea of an ordered sequence built into it.
Daniel Earwicker
Order is like an implementation detail for *foreach*. You can change collection type (to something without guaranteed order) without changing *foreach* syntax. The result will compile, but won't work as desired if you rely on elements indices (Odd) in *foreach* clause.
ima
@ima: Order *is not* an implementation detail, `foreach` iterates through the sequence *in order*. Whether the sequence being iterated has any meaningful order is a separate issue.
LukeH
Luke, you pointed at the problem, but somehow don't think it's relevant - but it often is. *Foreach* will iterate through sequence in order, no question about that. And you can implement, say, *Odd* event in *foreach*. And it will work without compile warnings, even if order of particular collection is undefined - that the problem I'm talking about, a recipe for some nasty run-time bugs.
ima
Semantically, foreach conditions should depend only on properties of items in collection, not on their indices. If you want to use foreach everywhere (it can be a good practice), it's better done by designing your program in such a way that you don't need to rely on indices anywhere. The trivial approach is to include important indices into properties of item objects.
ima