41

I have three (it's possible to have more than 3-4 generic list, but in this example let 3) generic lists.

List<string> list1

List<string> list2

List<string> list3

all lists have same number of elements (same counts).

I used that for combining two lists with ZIP :

var result = list1.Zip(list2, (a, b) => new {
  test1 = f,
  test2 = b
}

I used that for foreach statement, to avoid foreach each List, like

foreach(var item in result){
Console.WriteLine(item.test1 + " " + item.test2);
}

How to use simmilary with Zip for three lists ?

Thanks

EDIT:

I want like:

List<string> list1 = new List<string>{"test", "otherTest"};

List<string> list2 = new List<string>{"item", "otherItem"};

List<string> list3 = new List<string>{"value", "otherValue"};

after ZIP (I don't know method), I want to result (in VS2010 debug mode)

[0] { a = {"test"},
      b = {"item"},
      c = {"value"}
    }   

[1] { a = {"otherTest"},
      b = {"otherItem"},
      c = {"otherValue"}
    }  

How to do that ?

Onur
  • 5,017
  • 5
  • 38
  • 54
Snake Eyes
  • 16,287
  • 34
  • 113
  • 221
  • Possible duplicate of [Create Items from 3 collections using Linq](http://stackoverflow.com/questions/5284315/create-items-from-3-collections-using-linq) – Robert Synoradzki Jul 11 '16 at 10:57
  • Does this answer your question? [How to "zip" or "rotate" a variable number of lists?](https://stackoverflow.com/questions/17976823/how-to-zip-or-rotate-a-variable-number-of-lists) – GSerg Jun 25 '21 at 17:31

8 Answers8

48

The most obvious way for me would be to use Zip twice.

For example,

var results = l1.Zip(l2, (x, y) => x + y).Zip(l3, (x, y) => x + y);

would combine (add) the elements of three List<int> objects.

Update:

You could define a new extension method that acts like a Zip with three IEnumerables, like so:

public static class MyFunkyExtensions
{
    public static IEnumerable<TResult> ZipThree<T1, T2, T3, TResult>(
        this IEnumerable<T1> source,
        IEnumerable<T2> second,
        IEnumerable<T3> third,
        Func<T1, T2, T3, TResult> func)
    {
        using (var e1 = source.GetEnumerator())
        using (var e2 = second.GetEnumerator())
        using (var e3 = third.GetEnumerator())
        {
            while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())
                yield return func(e1.Current, e2.Current, e3.Current);
        }
    }
}

The usage (in the same context as above) now becomes:

var results = l1.ZipThree(l2, l3, (x, y, z) => x + y + z);

Similarly, you three lists can now be combined with:

var results = list1.ZipThree(list2, list3, (a, b, c) => new { a, b, c });
Cristian Lupascu
  • 39,078
  • 16
  • 100
  • 137
10

With .NET 6

.. and beyond Zip can be used to produce a sequence of tuples with elements from three specified sequences. [doc]

var titles = new string[] { "Analyst", "Consultant", "Supervisor"};
var names = new string[] { "Adam", "Eve", "Michelle" };
var surnames = new string[] { "First", "Second", "Third" };

IEnumerable<(string Title, string Name, string Surname)> zip = titles.Zip(names, surnames);
Nishan
  • 3,644
  • 1
  • 32
  • 41
4

There is another quite interesting solution that I'm aware of. It's interesting mostly from educational perspective but if one needs to perform zipping different counts of lists A LOT, then it also might be useful.

This method overrides .NET's LINQ SelectMany function which is taken by a convention when you use LINQ's query syntax. The standard SelectMany implementation does a Cartesian Product. The overrided one can do zipping instead. The actual implementation could be:

static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source,
        Func<TSource, IEnumerable<TCollection>> selector, Func<TSource, TCollection, TResult> select)
{
    using (var e1 = source.GetEnumerator())
        using (var e2 = selector(default(TSource)).GetEnumerator())
            while (true)
                if (e1.MoveNext() && e2.MoveNext())
                    yield return select(e1.Current, e2.Current);
                else
                    yield break;
}

It looks a bit scary but it is a logic of zipping which if written once, can be used in many places and the client's code look pretty nice - you can zip any number of IEnumerable<T> using standard LINQ query syntax:

var titles = new string[] { "Analyst", "Consultant", "Supervisor"};
var names = new string[] { "Adam", "Eve", "Michelle" };
var surnames = new string[] { "First", "Second", "Third" };

var results =
    from title in titles
    from name in names
    from surname in surnames
    select $"{ title } { name } { surname }";

If you then execute:

foreach (var result in results)
    Console.WriteLine(result);

You will get:

Analyst Adam First
Consultant Eve Second
Supervisor Michelle Third

You should keep this extension private within your class because otherwise you will radically change behavior of surrounding code. Also, a new type will be useful so that it won't colide with standard LINQ behavior for IEnumerables.

For educational purposes I've created once a small c# project with this extension method + few benefits: https://github.com/lukiasz/Zippable

Also, if you find this interesting, I strongly recommend Jon Skeet's Reimplementing LINQ to Objects articles.

Have fun!

lukiasz
  • 97
  • 1
  • 4
  • 5
    I've tried your example, but it gives every possible combination of list values rather than the zipped result you show. – bobasaurus Nov 21 '18 at 00:34
3

It is one of those cases where we need to decide if to favor code with better readability vs. shorter code with Linq, I preferred code readability.

class Program
{
    static void Main(string[] args)
    {
        List<string> list1 = new List<string> { "test", "otherTest" };
        List<string> list2 = new List<string> { "item", "otherItem" };
        List<string> list3 = new List<string> { "value", "otherValue" };

        var result = CombineListsByLayers(list1, list2, list3);
    }

    public static List<string>[] CombineListsByLayers(params List<string>[] sourceLists)
    {
        var results = new List<string>[sourceLists[0].Count];

        for (var i = 0; i < results.Length; i++)
        {
            results[i] = new List<string>();
            foreach (var sourceList in sourceLists)
                results[i].Add(sourceList[i]);
        }
        return results;
    }
G.Y
  • 6,042
  • 2
  • 37
  • 54
3

You can combine many lists in C# with cascade zip methods and anonymous classes and Tuple result.

List<string> list1 = new List<string> { "test", "otherTest" };
List<string> list2 = new List<string> { "item", "otherItem" };
List<string> list3 = new List<string> { "value", "otherValue" };

IEnumerable<Tuple<string, string, string>> result = list1
    .Zip(list2, (e1, e2) => new {e1, e2})
    .Zip(list3, (z1, e3) => Tuple.Create(z1.e1, z1.e2, e3));

The result is:

[0]
{(test, item, value)}
    Item1: "test"
    Item2: "item"
    Item3: "value"
andrew.fox
  • 7,435
  • 5
  • 52
  • 75
  • 2
    On a side-note: it is easier to use [Tuple.Create](https://msdn.microsoft.com/en-us/library/dd383822(v=vs.110).aspx), then you do not have to specify the generic parameters. – Maarten Aug 17 '16 at 07:05
3

Generic solution for any number of lists of different sizes to zip:

public static IEnumerable<TItem> ZipAll<TItem>(this IReadOnlyCollection<IEnumerable<TItem>> enumerables)
{
   var enumerators = enumerables.Select(enumerable => enumerable.GetEnumerator()).ToList();
   bool anyHit;
   do
   {
      anyHit = false;
      foreach (var enumerator in enumerators.Where(enumerator => enumerator.MoveNext()))
      {
          anyHit = true;
          yield return enumerator.Current;
      }
   } while (anyHit);

   foreach (var enumerator in enumerators)
   {
      enumerator.Dispose();
   }
}
Mugen
  • 8,301
  • 10
  • 62
  • 140
0

You can combine these List<string>'s into List<List<string>> and aggregate it

List<string> list1 = new List<string> { "test", "otherTest" };
List<string> list2 = new List<string> { "item", "otherItem" };
List<string> list3 = new List<string> { "value", "otherValue" };

var list = new List<List<string>>() { list1, list2, list3 }
    .Aggregate(
        Enumerable.Range(0, list1.Count).Select(e => new List<string>()),
        (prev, next) => prev.Zip(next, (first, second) => { first.Add(second); return first; })
    )
    .Select(e => new
    {
        a = e.ElementAt(0),
        b = e.ElementAt(1),
        c = e.ElementAt(2)
    });

Result

[
  {
    "a": "test",
    "b": "item",
    "c": "value"
  },
  {
    "a": "otherTest",
    "b": "otherItem",
    "c": "otherValue"
  }
]

See on dotnetfiddle.net

giokoguashvili
  • 2,013
  • 3
  • 18
  • 37
0

Solution using anonymous tuples (.Net 4 or greater I think):

List<Object1> list1;
List<Object2> list2;
List<Object3> list3;

foreach (var ((first, second), third) in list1.Zip(list2).Zip(list3))
{
   // first is an Object1
   // second is an Object2
   // third is an Object3
}
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 04 '22 at 00:23