Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform a collection

Tags:

c#

.net

linq

Have a collection of objects. Schematically:

[
    { A = 1, B = 1 }
    { A = 1, B = 2 }
    { A = 2, B = 3 }
    { A = 2, B = 4 }
    { A = 1, B = 5 }
    { A = 3, B = 6 }
]

Need:

[
    { A = 1, Bs = [ 1, 2 ] }
    { A = 2, Bs = [ 3, 4 ] }
    { A = 1, Bs = [ 5 ] }
    { A = 3, Bs = [ 6 ] }
]

Is it possible to LINQ such?

Note: Ordering is important. So Bs = [5] can't be merged with Bs = [1, 2]

like image 522
Sergey Metlov Avatar asked Jul 16 '12 21:07

Sergey Metlov


2 Answers

Given these simplistic classes:

class C {
  public int A;
  public int B;
}
class R {
  public int A;
  public List<int> Bs = new List<int>();
}

You can do it like this:

var cs = new C[] {
  new C() { A = 1, B = 1 },
  new C() { A = 1, B = 2 },
  new C() { A = 2, B = 3 },
  new C() { A = 2, B = 4 },
  new C() { A = 1, B = 5 },
  new C() { A = 3, B = 6 }
};

var rs = cs.
  OrderBy(o => o.B).
  ThenBy(o => o.A).
  Aggregate(new List<R>(), (l, o) => {
    if (l.Count > 0 && l.Last().A == o.A) {
      l.Last().Bs.Add(o.B);
    }
    else {
      l.Add(new R { A = o.A, Bs = { o.B } });
    }
    return l;
  });

Note: In the above I assume that the Bs and then the As have to be sorted. If that's not the case, it's a simple matter of removing the sorting instructions:

var rs = cs.
  Aggregate(new List<R>(), (l, o) => {
    if (l.Count > 0 && l.Last().A == o.A) {
      l.Last().Bs.Add(o.B);
    }
    else {
      l.Add(new R { A = o.A, Bs = { o.B } });
    }
    return l;
  });
like image 157
Jordão Avatar answered Nov 09 '22 02:11

Jordão


So basically you want to group together what has the same A-value and is consecutive.

You need to tranform the list of objects to an anonymous type which contains the previous/next element. I've used two Selects to make it more redable. Then you need to check if the two elements are consecutive(adjacent indices). Now you have all you need to GroupBy, the value and the bool.

Your objects:

var list = new System.Collections.Generic.List<Foo>(){
    new Foo(){ A = 1, B = 1 },
    new Foo(){ A = 1, B = 2 },
    new Foo(){ A = 2, B = 3 },
    new Foo(){ A = 2, B = 4 },
    new Foo(){ A = 1, B = 5 },
    new Foo(){ A = 3, B = 6 }
};

The query:

var groups = list
    .Select((f, i) => new
    {
        Obj = f,
        Next = list.ElementAtOrDefault(i + 1),
        Prev = list.ElementAtOrDefault(i - 1)
    })
    .Select(x => new
    {
        A = x.Obj.A,
        x.Obj,
        Consecutive = (x.Next != null && x.Next.A == x.Obj.A)
                   || (x.Prev != null && x.Prev.A == x.Obj.A)
    })
    .GroupBy(x => new { x.Consecutive, x.A });

Output the result:

foreach (var abGroup in groups)
{
    int aKey = abGroup.Key.A;
    var bList = string.Join(",", abGroup.Select(x => x.Obj.B));
    Console.WriteLine("A = {0}, Bs = [ {1} ] ", aKey, bList);
}

Here's the working demo: http://ideone.com/fXgQ3

like image 37
Tim Schmelter Avatar answered Nov 09 '22 01:11

Tim Schmelter