Перебрать несколько списков

учитывая кучу списков, мне нужно перебирать их одновременно. Предположим, у меня их три:--1-->, list2 и list3.

то, что я нашел до сих пор является следующее:

foreach (var tuple in list1.Zip(list2, (first, second) => new { object1 = first, object2 = second })
                          .Zip(list3, (first, second) => new { object1 = first.object1, object2 = first.object2, object3 = second }))
{
  //do stuff
}

это отлично работает и вполне читаемо, если количество списков не велико. Я знаю, как расширить его до 4, 5,.... списки, но если я zip 10 из них, код будет очень длинным. Есть ли возможность переделать его? Или мне нужно другое решение, чем

4 ответов


С помощью немного генерации кода (думаю, T4), можно было бы произвести до 6 перегрузок (потому что Tuple ограничивается 7 общими аргументами) чего - то похожего на:

public static class Iterate
{
    public static IEnumerable<Tuple<T1, T2, T3>> Over<T1, T2, T3>(IEnumerable<T1> t1s, IEnumerable<T2> t2s, IEnumerable<T3> t3s)
    {
        using(var it1s = t1s.GetEnumerator())
        using(var it2s = t2s.GetEnumerator())
        using(var it3s = t3s.GetEnumerator())
        {
            while(it1s.MoveNext() && it2s.MoveNext() && it3s.MoveNext())
                yield return Tuple.Create(it1s.Current, it2s.Current, it3s.Current);
        }
    }
}

С Iterate класс, итерация становится очень простой:

foreach(var t in Iterate.Over(
    new[] { 1, 2, 3 }, 
    new[] { "a", "b", "c" }, 
    new[] { 1f, 2f, 3f }))
{
}

Это может быть далее обобщена (с полной потерей безопасности типа):

public static IEnumerable<object[]> Over(params IEnumerable[] enumerables)

почему не старый добрый for петли?

  int n = new int[] {
    list1.Count,
    list2.Count,
    list3.Count,
    // etc.
  }.Min(); // if lists have different number of items

  for (int i = 0; i < n; ++i) {
    var item1 = list1[i]; // if you want an item
    ...
  }

насколько я понимаю, реальная проблема заключается в неизвестном количестве списков для итерации. Еще одна проблема, которую я вижу, заключается в том, что нет гарантии, что все списки будут иметь одинаковую длину... правильно?

Если количество списков неизвестно, кортежи не будут этого делать, потому что они дойдут до 8... и должен быть установлен во время компиляции...

в этом случае я бы предложил вам вместо сопоставления с кортежем сделать это с простой и очень старой структурой: матрицей! Ширина будет номер списка (известный во время выполнения) и глубина будут самым длинным списком. Вы можете повторить, используя простой и хорошо знать for, пусть компилятор оптимизирует память и распределение... Код будет очень удобочитаем не только людьми C#, но и практически для всех, кто работает с любым языком программирования...


добавление к ответу @AntonGogolev, на его последнее замечание... если вы не заботитесь о безопасности типов и производительности (для бокса-распаковки), вы можете реализовать перечислитель с помощью object[]:

public static class Iterator
{
   public static IEnumerable<object[]> Enumerate(params IEnumerable[] enumerables)
   {
     var list = new List<object>();
     var enumerators = new List<IEnumerator>();
     bool end = false;

     foreach(var enu in enumerables)
     {
       enumerators.Add(enu.GetEnumerator());
     }
     while(!end)
     {
       list.Clear();
       foreach(var enu in enumerators)
       {
           if(!enu.MoveNext()) { end = true; break; }
           list.Add(enu.Current);                  
       }
       if(!end) yield return list.ToArray();           
     }
   }
}

предупреждение: никаких усилий не было сделано для оптимизации этого кода, и он был написан так, как он прошел через пальцы : -)

вы можете использовать его как:

var listA = new[] { 1, 2, 3 };
var listB = new[] { "a", "b", "c" };
var listC = new[] { 5f, 6f, 7f };
foreach(var n in Iterator.Enumerate(listA, listB, listC))
{
    foreach(var obj in n)
    {
        Console.Write(obj.ToString() + ", ");
    }
    Console.WriteLine();
}

скрипка здесь:https://dotnetfiddle.net/irTY8M