tags:

views:

59

answers:

3

This question is similar to http://stackoverflow.com/questions/2835192/linq-group-one-type-of-item but handled in a more generic way.

I have a List that has various derived classes. I may have something like this:

List<BaseClass> list = new List<BaseClass>() {
  new Class1(1),
  new Class2(1),
  new Class1(2),
  new Class3(1),
  new Class2(2),
  new Class4(1),
  new Class3(2)
};

I am trying to use LINQ to semi-sort the list so that the natural order is maintained EXCEPT for certain classes which have base.GroupThisType == true. All classes with GroupThisType should be grouped together at the place that the first class of the same type occurs. Here is what the output should be like:

List<BaseClass> list = new List<BaseClass>() {
  new Class1(1),
  new Class1(2),
  new Class2(1),
  new Class3(1),
  new Class3(2)
  new Class2(2),
  new Class4(1),
};

Edit: Oops, forgot to say this result is assuming (Class1 and Class3).GroupThisType == true

A: 

Hey,

The OrderBy LINQ method can accept an IComparer generic interface. You can use this to implement your custom sorting algorithm. I don't know the default ordering can handle what you are trying to do (it would depend on all the rules you need to implement). I assume you're classes aren't actually named Class1, Class2 with the ordering in the type name?

HTH.

Brian
He wants to preserve the original order, but move all items of certain types to the position of the first occurrence. It is not possible to do that with an `IComparer`.
SLaks
A: 

Like this:

list = list.Select((o, i) => new { Index = i * 10000, Value = o })
           .GroupBy(q => q.GetType())
           .SelectMany(g => {
               if (g.First().GroupThisType)
                   return g.Select((q, i) => 
                       new { Index = g.First().Index + i, Value = q.Value }
                   );
               else
                   return g;
           })
           .OrderBy(q => q.Index)
           .Select(q => q.Value)
           .ToList();

The i * 10000 allows up to 10,000 items from a group to be inserted between any two items.

You can replace g.First().GroupThisType with typesToGroup.Contains(g.Key).

SLaks
I'm starting to think a more traditional approach would be easier to understand :) Also, by doing i * 10000 you're limiting how many total items there can be. I'm guessing i is an int, therefore 2147483647 / 10000 = 214748 max items. Still WAY over what I would ever have.
Nelson
`i` is an `int`, but you can cast it to a `long`.
SLaks
+1  A: 

Here's a solution using two passes: in the first I build a dictionary of all the ones that should group. In the second I use SelectMany to gather up the elements that don't collate with the collated sequences for the first of any element that does collate.

// Build a dictionary of the items that group
var onesToGroup = list.Where(x => x.GroupThisClass)
                            .GroupBy(x => x.GetType())
                            .ToDictionary(x => x.Key, x => x.AsEnumerable());

var results = list.SelectMany(x => x.GroupThisClass ?
                             (onesToGroup[x.GetType()].First() == x ? onesToGroup[x.GetType()] : (new BaseClass[]{}))
                                                : (new []{x}));
Hightechrider
But it will, I'm not iterating over the dictionary, I'm iterating over the original list and using the dictionary only to find the relevant group.
Hightechrider
Yes, you're right. I misunderstood.
SLaks
It does however rely on GroupBy being in order ... which luckily MSDN confirms is the case.
Hightechrider