Пропуск через 2 списка сразу

17

У меня есть два списка одинаковой длины, можно ли сразу пропустить эти два списка?

Я ищу правильный синтаксис, чтобы сделать ниже

foreach itemA, itemB in ListA, ListB
{
  Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}

Как вы думаете, это возможно на C #? И если это так, каково эквивалент лямбда-выражения?

    
задан Graviton 28.10.2008 в 07:51
источник
  • Вы хотите объединить соответствующие элементы каждого списка? Или вы хотите декартово произведение списков? –  Vincent Ramdhanie 28.10.2008 в 07:54
  • @Vincent: поскольку он упоминает ту же длину, я предполагаю, что первый. –  Marc Gravell♦ 28.10.2008 в 08:03
  • Ни то, ни другое. Но это не имеет значения, не так ли? –  Graviton 28.10.2008 в 08:10
  • Как это может быть и? Вы хотите, чтобы первый из списка ListA & ListB, а затем второй из списка ListA & ListB и т. Д.? –  Marc Gravell♦ 28.10.2008 в 08:15

7 ответов

24

[править]: уточнить; это полезно в общем контексте LINQ / IEnumerable<T> , где вы не можете использовать индексатор, потому что a: он не существует в перечислимом, а b: вы не можете гарантировать, что вы можете читать данные более одного раза. Поскольку OP упоминает лямбда, происходит, что LINQ может быть не слишком далеко (и да, я понимаю, что LINQ и lambdas - не совсем одно и то же).

Похоже, вам нужен отсутствующий оператор Zip ; вы можете обмануть его:

static void Main()
{
    int[] left = { 1, 2, 3, 4, 5 };
    string[] right = { "abc", "def", "ghi", "jkl", "mno" };

    // using KeyValuePair<,> approach
    foreach (var item in left.Zip(right))
    {
        Console.WriteLine("{0}/{1}", item.Key, item.Value);
    }

    // using projection approach
    foreach (string item in left.Zip(right,
        (x,y) => string.Format("{0}/{1}", x, y)))
    {
        Console.WriteLine(item);
    }
}

// library code; written once and stuffed away in a util assembly...

// returns each pais as a KeyValuePair<,>
static IEnumerable<KeyValuePair<TLeft,TRight>> Zip<TLeft, TRight>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right)
{
    return Zip(left, right, (x, y) => new KeyValuePair<TLeft, TRight>(x, y));
}

// accepts a projection from the caller for each pair
static IEnumerable<TResult> Zip<TLeft, TRight, TResult>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right,
    Func<TLeft, TRight, TResult> selector)
{
    using(IEnumerator<TLeft> leftE = left.GetEnumerator())
    using (IEnumerator<TRight> rightE = right.GetEnumerator())
    {
        while (leftE.MoveNext() && rightE.MoveNext())
        {
            yield return selector(leftE.Current, rightE.Current);
        }
    }
}
    
ответ дан Marc Gravell 28.10.2008 в 07:54
  • Я отредактировал, чтобы добавить контекст ... –  Marc Gravell♦ 28.10.2008 в 08:02
  • Приятно, но есть ли причина сделать его методом расширения? –  peterchen 28.10.2008 в 08:40
  • , он делает его более доступным - т. е. ударяет "." и кажется, в отличие от того, чтобы знать о SomeUtilityClass.Zip (...); он также отлично подходит для других методов расширения для IEnumerable <T>, поэтому мне нравится это как метод расширения. –  Marc Gravell♦ 28.10.2008 в 08:42
  • Альтернативой KeyValuePair является, конечно, Func <TLeft, TRight, TResult>. –  Jon Skeet 28.10.2008 в 08:44
  • peterchen: Почему бы вам не захотеть использовать этот запрос LINQ? :) –  Jon Skeet 28.10.2008 в 08:45
Показать остальные комментарии
11

Проще будет просто сделать это в простой старой для цикла ...

for(int i=0; i<ListA.Length; i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
    
ответ дан jcelgin 28.10.2008 в 07:56
  • Да, нет необходимости менять четыре строки кода на 13. –  Ed S. 28.10.2008 в 07:58
  • Разница в том, что вам нужен только метод расширения Zip, и вы можете повторно использовать его навсегда. Более того, он будет работать на любой последовательности, а не только на списках. –  Jon Skeet 28.10.2008 в 08:02
  • Я был пристыжен Джон Скит :( –  jcelgin 02.02.2009 в 03:53
5

Вы можете сделать это явным.

IEnumerator ListAEnum = ListA.GetEnumerator();
IEnumerator ListBEnum = ListB.GetEnumerator();

ListBEnum.MoveNext();
while(ListAEnum.MoveNext()==true)
{
  itemA=ListAEnum.getCurrent();
  itemB=ListBEnum.getCurrent();
  Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}

По крайней мере, это (или что-то вроде этого) - это то, что компилятор делает для цикла foreach. Я не тестировал его, и, наверное, некоторые параметры шаблона отсутствуют для счетчиков.

Просто найдите GetEnumerator () из списка и интерфейса IEnumerator.

    
ответ дан Tobias Langner 28.10.2008 в 08:06
  • Вы не использовали MoveNext в ListBEnum - и вы определенно должны использовать «using»; возможно, посмотрите на подход Zip. –  Marc Gravell♦ 28.10.2008 в 08:12
  • Это не плохой подход, ему просто нужно перемещать ListBEnum.MoveNext внутри цикла while. Это также более общий, чем метод Zip. –  cdmckay 02.09.2009 в 20:21
1

Я рекомендую использовать простой старый цикл, но вы должны учитывать разные длины массивов. Итак,

for(int i=0; i<ListA.Length; i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}

может превратиться в

for(int i = 0; i < Math.Min(ListA.Length, ListB.Lenght); i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}

или даже в

    for(int i = 0; i < Math.Max(ListA.Length, ListB.Lenght); i++)
    {
        string valueA = i < ListA.Length ? listA[i].ToString() : "";
        string valueB = i < ListB.Length ? listB[i].ToString() : "";

        Console.WriteLine(valueA+ ", " + valueB);
    }
    
ответ дан PiRX 28.10.2008 в 09:34
1

У меня была эта же проблема, но с использованием списков объектов со списками внутри них .. для чего это стоит, это может помочь кому-то с той же проблемой.

Время выполнения этого не очень хорошо, так как IndexOf равно O (n), но в то время я занимаюсь гораздо более внутренними циклами foreach, чем в этом примере, поэтому я не хотел обрабатывать переменные итератора.

В то же время я очень скучаю по PHP foreach ($ arrayList как $ key = & gt; $ value) нотация ... может быть, я что-то пропустил в C #, должен быть способ получить индекс в O (c) время! (к сожалению, этот пост говорит нет: Получение ключа массива в 'foreach' петля )

class Stock {
   string symbol;
   List<decimal> hourlyPrice; // provides a list of 24 decimals
}

// get hourly prices from yesterday and today
List<Stock> stockMondays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now.AddDay(-1));
List<Stock> stockTuesdays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now);

try {
    foreach(Stock sMonday in stockMondays) {
        Stock sTuesday = stockTuesday[stockMondays.IndexOf(sMonday)];

        foreach(decimal mondayPrice in sMonday.prices) {
            decimal tuesdayPrice = sTuesday.prices[sMonday.prices.IndexOf(mondayPrice)];
            // do something now
        }

    }
} catch (Exception ex) { // some reason why list counts aren't matching? }
    
ответ дан sonjz 29.03.2012 в 20:57
0

Технический блог Senthil Kumar , имеет серию, охватывающую реализацию (Python) Itertools для C # , включая itertools.izip .

Из Itertools for C # - цикл и Zip , у вас есть решение для любого количества iterables (не только List & amp; LT; T & amp; GT; ). Обратите внимание, что Zip дает Array на каждой итерации:

public static IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables)
{
IEnumerator<T>[] enumerators = Array.ConvertAll(iterables, (iterable) =>   iterable.GetEnumerator());

while (true)
{
   int index = 0;
   T[] values = new T[enumerators.Length];

   foreach (IEnumerator<T> enumerator in enumerators)
   {
       if (!enumerator.MoveNext())
          yield break;

        values[index++] = enumerator.Current;
   }

   yield return values;
}

}

  

Код получает перечисление для всех итераций, перемещает все перечисления вперед, накапливает их текущие значения в массив и дает массив. Он делает это до тех пор, пока ни один из счетчиков не исчерпывается элементами.

    
ответ дан gimel 28.10.2008 в 08:38
  • Хорошая идея; хотя два варианта реализации. Сначала он не распоряжается счетчиками (в основном проблема для исключений). Но что более важно, повторное использование массива является рискованным: если я назову .ToArray () /. ToList (), у меня будет 30 ссылок на один и тот же массив, и нет возможности получить ранние данные. –  Marc Gravell♦ 28.10.2008 в 08:45
  • Согласен, но обратите внимание, что для каждого выхода создается новый массив (может быть заменен List <T> или другим IEnumerable). –  gimel 28.10.2008 в 10:58
  • О, верно! Виноват. Я думал, что Т [] был снаружи. Кодовая слепота. –  Marc Gravell♦ 28.10.2008 в 13:32
0

У меня есть эта небольшая функция, которая помогает мне перебирать эти два объекта списка. схема имеет тип SqlData, который является классом, который содержит три свойства. И данные - это список, который содержит значения динамического типа. Сначала я выполняю итерацию через сборку схемы, а не используя индекс элемента для итерации через объект данных.

public List<SqlData> SqlDataBinding(List<SqlData> schema, List<dynamic> data)
{
    foreach (SqlData item in schema)
    {
        item.Values = data[schema.IndexOf(item)];
    }
    return schema
}
    
ответ дан Joy Fernandes 08.01.2017 в 10:06