11

I have a List<Tuple<A,B>> and would like to know if there is a way in LINQ to return Tuple<List<A>,List<B>>

This is similar to the following Python question: Unpacking a list / tuple of pairs into two lists / tuples

Community
  • 1
  • 1
Ben
  • 2,430
  • 3
  • 22
  • 24

4 Answers4

12

It's not possible with a single LINQ call, however you can do it quite easily with code:

Tuple<List<A>, List<B>> Unpack<A, B>(List<Tuple<A, B>> list)
{
  var listA = new List<A>(list.Count);
  var listB = new List<B>(list.Count);
  foreach (var t in list)
  {
    listA.Add(t.Item1);
    listB.Add(t.Item2);
  }

  return Tuple.Create(listA, listB);
}
Martin1921
  • 653
  • 1
  • 9
  • 12
  • 3
    This is probably the most simple way of doing it without enumerating over the list twice. – vcsjones Jul 27 '12 at 14:03
  • Why do you initialize the capacity of the two lists? Is there a performance benefit? – Aducci Jul 27 '12 at 14:04
  • 4
    @Aducci Yes, you aren't resizing the internal buffer continually. – Servy Jul 27 '12 at 14:05
  • 1
    @Aducci A list has an internal buffer that can hold up to N elements. When you add more elements and the buffer becomes to small, then a new buffer will be created, all elements copied over and the new buffer is used - this is a slow process. When you know the amount of elements you can tell the List "Hey, I want you to initially allocate enough memory to hold N elements", thus there won't be any new allocation necessary. – Martin1921 Jul 27 '12 at 14:11
  • It might be even more efficient to use [`List.AddRange`](http://msdn.microsoft.com/en-us/library/z883w3dc.aspx) after you've set the capacity instead of the `foreach`, for example: `listA.AddRange(list.Select(t => t.Item1));` – Tim Schmelter Jul 27 '12 at 14:12
  • 1
    It can be written slightly more tersely but in essence is exactly the same: `var unpacked = new Tuple, List>(new List(pairs.Count), new List(pairs.Count)); pairs.ForEach(pair => { unpacked.Item1.Add(pair.Item1); unpacked.Item2.Add(pair.Item2); });` – Adam Houldsworth Jul 27 '12 at 14:12
  • 3
    @TimSchmelter That way you'd have to enumerate the list twice again, which I wanted to prevent. – Martin1921 Jul 27 '12 at 14:14
  • @Servy martine1921- Interesting, it is a nice little optimization. thank you. – Aducci Jul 27 '12 at 14:18
  • @AdamHouldsworth That is no more concise than the code posted here. If you remove the whitespace from this code it is just as compact. – Servy Jul 27 '12 at 14:23
  • @Martin1921 It's actually not really much of an enumeration. Enumerating a list twice is almost the same as enumerating a list twice and doing half as much work each time. You have a little extra overhead of manipulating the loop variables twice, but that's pretty small. It's the general reason why LINQ isn't all that efficient. Having lots of loops that don't do a lot is only a tad less efficient than one loop that does it all. – Servy Jul 27 '12 at 14:25
  • @Servy I beg to differ, mine chops out one line :-) but I actually meant terse in terms of local variables. – Adam Houldsworth Jul 27 '12 at 14:27
6

You can use Aggregate:

Tuple<List<A>, List<B>> Unpack<A, B>(List<Tuple<A, B>> list)
{
    return list.Aggregate(Tuple.Create(new List<A>(list.Count), new List<B>(list.Count)),
                          (unpacked, tuple) =>
                          {
                              unpacked.Item1.Add(tuple.Item1);
                              unpacked.Item2.Add(tuple.Item2);
                              return unpacked;
                          });
}
Risky Martin
  • 2,491
  • 2
  • 15
  • 16
2
Tuple<List<A>, List<B>> Unpack<A, B>(List<Tuple<A, B>> list) {
            return Tuple.Create(list.Select(e => e.Item1).ToList(),list.Select(e => e.Item2).ToList());
}
Gerberts
  • 29
  • 1
-2
public Tuple<List<int>,List<int>> Convert(List<Tuple<int, int>> BaseList)
{
    return new Tuple<List<int>,List<int>>((from tuple in BaseList
                                                        select tuple.Item1).ToList(),
                                                        (from tuple in BaseList
                                                        select tuple.Item2).ToList());
}

I considered it is a list of integer

Y2theZ
  • 10,162
  • 38
  • 131
  • 200