tags:

views:

78

answers:

2

I have a List<T> that is a mixture of 'regular' elements and 'marker' elements. I want to split it into a List<List<T>>, broken at the markers.

e.g. given an input of {a, b, M, c, d, e, f, M, g}, where 'M' is a marker element, I want to get {{a, b},{c, d, e, f}, {g}}

It is easy to do the job with a loop, but it seems like it should be possible to express it more compactly with LINQ, and after a bit of head-scratching I just can't see how. (Not enough LINQ-fu, I guess.)

+5  A: 

Hmm...

varSplitList = myList.Aggregate(new List<List<T>>{new List<T>()}, 
   (sl,t)=> {
      if(/*T is a marker element*/) sl.Add(new List<T>());
      else sl.Last().Add(t); 
      return sl;
   });

There's probably a more readable way, but that should work. Aggregate is a very powerful "series calculation" method that is useful for these kinds of things. You provide it a "seed" (in this case a new List of Lists with a single child List), and for each element in the source, perform the operation, which takes the seed and the current element and returns the (probably modified) seed, which is then passed to the operation for the next element.

As a simpler example, here's a Factorial calculator in Linq:

long fact = Enumerable.Range(1,n).Aggregate(1, (f,n)=>f*n));

Enumerable.Range() produces an integer range from 1 to n. the Aggregate function starts with 1, and for each element, multiplies the seed by the element:

1*1=1 1*2=2 2*3=6 6*4=24 24*5=120...

At the end, fact is given the value of the seed after all calculations are performed.

KeithS
I'll give you style points (and an upvote), but I would say that this isn't obviously more compact than the equivalent loop implementation - it's pretty much just the loop in LINQ clothing.
McKenzieG1
Very true... of practially all LINQ methods. Seriously, LINQ is a library of iterator-hiders.
KeithS
+2  A: 

Doesn't seem like a job for LINQ. But if I were to write it without writing "a loop in LINQ clothing", I would do something like this:

var list = new List<char> {'a', 'b', 'M', 'c', 'd', 'e', 'f', 'M', 'g'};
const char marker = 'M';

var markerIndexes = list.Select((c, i) => new { c, i }).Where(z => z.c == marker).Select(z => z.i);
var split = from z in list.Select((c, i) => new { c, i })
            where z.c != marker
            group z.c by markerIndexes.Count(mi => z.i > mi) into g
            select g.ToList();
return split.ToList();
Michael Kropat
Quite ingenious, but also (paradoxically?) somewhat awful. Certainly a lot harder to understand than a plain-vanilla loop implementation, and no more concise. Based on the answers so far, it looks like you are right - "doesn't seem like a job for LINQ". I have learned some things from studying the answer, though - thank you.
McKenzieG1