Как перебирать сразу два массива?

У меня есть два массива, созданных при parsingе текстового файла. Первый содержит имена столбцов, второй содержит значения из текущей строки. Мне нужно перебрать оба списка сразу, чтобы построить карту. Сейчас у меня есть следующее:

var currentValues = currentRow.Split(separatorChar); var valueEnumerator = currentValues.GetEnumerator(); foreach (String column in columnList) { valueEnumerator.MoveNext(); valueMap.Add(column, (String)valueEnumerator.Current); } 

Это работает отлично, но это не совсем удовлетворяет моему чувству элегантности, и он становится действительно волосатым, если количество массивов больше двух (как мне иногда приходится делать). У кого-нибудь есть другой, терпимый идиом?

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

     var currentValues = currentRow.Split(separatorChar); for(var i=0;i 

    У вас есть неочевидная псевдо-ошибка в вашем исходном коде – IEnumerator расширяет IDisposable поэтому вы должны его утилизировать. Это может быть очень важно с iteratorными блоками! Не проблема для массивов, но будет с другими реализациями IEnumerable .

    Я бы сделал это так:

     public static IEnumerable PairUp (this IEnumerable source, IEnumerable secondSequence, Func projection) { using (IEnumerator secondIter = secondSequence.GetEnumerator()) { foreach (TFirst first in source) { if (!secondIter.MoveNext()) { throw new ArgumentException ("First sequence longer than second"); } yield return projection(first, secondIter.Current); } if (secondIter.MoveNext()) { throw new ArgumentException ("Second sequence longer than first"); } } } 

    Затем вы можете повторно использовать это, когда у вас есть необходимость:

     foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar), (column, value) => new { column, value }) { // Do something } 

    В качестве альтернативы вы можете создать общий тип Pair и избавиться от параметра проекции в методе PairUp.

    РЕДАКТИРОВАТЬ:

    С помощью типа Pair код вызова будет выглядеть так:

     foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar)) { // column = pair.First, value = pair.Second } 

    Это выглядит так просто, как вы можете получить. Да, вам нужно куда-нибудь поместить утилиту, как многоразовый код. Вряд ли проблема на мой взгляд. Теперь для нескольких массивов …

    Если массивы имеют разные типы, у нас есть проблема. Вы не можете выразить произвольное количество параметров типа в объявлении generic method / type – вы могли бы писать версии PairUp для такого количества параметров типа, сколько захотите, так же как есть delegates Action и Func для до 4 параметров делегата, но вы не можете сделать это произвольным.

    Однако, если все значения будут одного типа, и если вы счастливы придерживаться массивов, это легко. (Не-массивы тоже в порядке, но вы не можете проверять длину раньше времени.) Вы могли бы сделать это:

     public static IEnumerable Zip(params T[][] sources) { // (Insert error checking code here for null or empty sources parameter) int length = sources[0].Length; if (!sources.All(array => array.Length == length)) { throw new ArgumentException("Arrays must all be of the same length"); } for (int i=0; i < length; i++) { // Could do this bit with LINQ if you wanted T[] result = new T[sources.Length]; for (int j=0; j < result.Length; j++) { result[j] = sources[j][i]; } yield return result; } } 

    Тогда код вызова:

     foreach (var array in Zip(columns, row, whatevers)) { // column = array[0] // value = array[1] // whatever = array[2] } 

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

     public struct Snapshot { readonly T[][] sources; readonly int index; public Snapshot(T[][] sources, int index) { this.sources = sources; this.index = index; } public T this[int element] { return sources[element][index]; } } 

    Это, вероятно, будет считаться чрезмерным большинством, хотя;)

    Если честно, я мог бы придумать всевозможные идеи, но основы:

    • С небольшим количеством многоразовой работы вы можете сделать код вызова более приятным
    • Для произвольных комбинаций типов вам нужно будет выполнять каждое число параметров (2, 3, 4 ...) отдельно из-за того, как работают дженерики
    • Если вы счастливы использовать один и тот же тип для каждой части, вы можете сделать лучше

    На функциональном языке вы обычно найдете функцию «zip», которая, мы надеемся, будет частью C # 4.0. Bart de Smet обеспечивает смешную реализацию zip на основе существующих функций LINQ:

     public static IEnumerable Zip( this IEnumerable first, IEnumerable second, Func func) { return first.Select((x, i) => new { X = x, I = i }) .Join(second.Select((x, i) => new { X = x, I = i }), o => oI, i => iI, (o, i) => func(oX, iX)); } 

    Тогда вы можете сделать:

      int[] s1 = new [] { 1, 2, 3 }; int[] s2 = new[] { 4, 5, 6 }; var result = s1.Zip(s2, (i1, i2) => new {Value1 = i1, Value2 = i2}); 

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

    Вы также можете инкапсулировать свой код в метод zip – это обычная функция списка более высокого порядка. Тем не менее, у C #, не имеющего подходящего типа Tuple, это довольно круто. Вы в конечном итоге IEnumerable> что не очень приятно.

    Кстати, вы действительно используете IEnumerable вместо IEnumerable или почему вы используете Current значение?

    Использовать IEnumerator для обоих было бы неплохо

     var currentValues = currentRow.Split(separatorChar); using (IEnumerator valueEnum = currentValues.GetEnumerator(), columnEnum = columnList.GetEnumerator()) { while (valueEnum.MoveNext() && columnEnum.MoveNext()) valueMap.Add(columnEnum.Current, valueEnum.Current); } 

    Или создайте методы расширения

     public static IEnumerable Zip(this IEnumerable source, IEnumerable other, Func selector) { using (IEnumerator sourceEnum = source.GetEnumerator()) { using (IEnumerator otherEnum = other.GetEnumerator()) { while (sourceEnum.MoveNext() && columnEnum.MoveNext()) yield return selector(sourceEnum.Current, otherEnum.Current); } } } 

    использование

     var currentValues = currentRow.Split(separatorChar); foreach (var valueColumnPair in currentValues.Zip(columnList, (a, b) => new { Value = a, Column = b }) { valueMap.Add(valueColumnPair.Column, valueColumnPair.Value); } 

    Вместо создания двух отдельных массивов вы могли бы создать двумерный массив или словарь (что было бы лучше). Но действительно, если это сработает, я бы не попытался изменить его.