Come eseguire l'iterazione su due array contemporaneamente?
-
20-08-2019 - |
Domanda
Ho due array creati durante l'analisi di un file di testo. Il primo contiene i nomi delle colonne, il secondo contiene i valori della riga corrente. Ho bisogno di scorrere contemporaneamente su entrambi gli elenchi per creare una mappa. In questo momento ho il seguente:
var currentValues = currentRow.Split(separatorChar);
var valueEnumerator = currentValues.GetEnumerator();
foreach (String column in columnList)
{
valueEnumerator.MoveNext();
valueMap.Add(column, (String)valueEnumerator.Current);
}
Funziona benissimo, ma non soddisfa del tutto il mio senso di eleganza e diventa davvero peloso se il numero di array è maggiore di due (come devo fare di tanto in tanto). Qualcuno ha un altro idioma più teso?
Soluzione
se esiste lo stesso numero di nomi di colonna degli elementi in ogni riga, non potresti utilizzare un ciclo for?
var currentValues = currentRow.Split(separatorChar);
for(var i=0;i<columnList.Length;i++){
// use i to index both (or all) arrays and build your map
}
Altri suggerimenti
Hai un pseudo-bug non ovvio nel tuo codice iniziale - IEnumerator<T>
estende IDisposable
quindi dovresti eliminarlo. Questo può essere molto importante con i blocchi iteratori! Non è un problema per gli array, ma lo sarebbe con altre IEnumerable<T>
implementazioni.
Lo farei così:
public static IEnumerable<TResult> PairUp<TFirst,TSecond,TResult>
(this IEnumerable<TFirst> source, IEnumerable<TSecond> secondSequence,
Func<TFirst,TSecond,TResult> projection)
{
using (IEnumerator<TSecond> 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");
}
}
}
Quindi puoi riutilizzarlo ogni volta che ne hai bisogno:
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar),
(column, value) => new { column, value })
{
// Do something
}
In alternativa, è possibile creare un tipo di coppia generico e sbarazzarsi del parametro di proiezione nel metodo PairUp.
EDIT:
Con il tipo Pair, il codice chiamante sarebbe simile al seguente:
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar))
{
// column = pair.First, value = pair.Second
}
Sembra più semplice che puoi ottenere. Sì, è necessario inserire il metodo di utilità da qualche parte, come codice riutilizzabile. Difficilmente un problema a mio avviso. Ora per più array ...
Se le matrici sono di tipo diverso, abbiamo un problema. Non è possibile esprimere un numero arbitrario di parametri di tipo in una dichiarazione di metodo / tipo generica: è possibile scrivere versioni di PairUp per tutti i parametri di tipo desiderati, proprio come ci sono delegati Action
e Func
per un massimo di 4 delegare i parametri, ma non è possibile renderlo arbitrario.
Se i valori saranno tutti dello stesso tipo, tuttavia, e se si è felici di attenersi agli array, è facile. (Anche i non array vanno bene, ma non è possibile eseguire il controllo della lunghezza in anticipo.) È possibile farlo:
public static IEnumerable<T[]> Zip<T>(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;
}
}
Quindi il codice chiamante sarebbe:
foreach (var array in Zip(columns, row, whatevers))
{
// column = array[0]
// value = array[1]
// whatever = array[2]
}
Ciò comporta ovviamente un certo numero di copie: ogni volta crei un array. Puoi cambiarlo introducendo un altro tipo come questo:
public struct Snapshot<T>
{
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];
}
}
Questo probabilmente sarebbe considerato eccessivo dalla maggior parte però;)
Potrei continuare a venire con tutti i tipi di idee, a dire il vero ... ma le basi sono:
- Con un po 'di lavoro riutilizzabile, puoi rendere più piacevole il codice chiamante
- Per combinazioni arbitrarie di tipi dovrai fare ogni numero di parametri (2, 3, 4 ...) separatamente a causa del modo in cui generics funziona
- Se sei felice di usare lo stesso tipo per ogni parte, puoi fare di meglio
In un linguaggio funzionale di solito si trova un " zip " funzione che si spera faccia parte di un C # 4.0. Bart de Smet fornisce una divertente implementazione di zip basata sulle funzioni LINQ esistenti:
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> func)
{
return first.Select((x, i) => new { X = x, I = i })
.Join(second.Select((x, i) => new { X = x, I = i }),
o => o.I,
i => i.I,
(o, i) => func(o.X, i.X));
}
Quindi puoi fare:
int[] s1 = new [] { 1, 2, 3 };
int[] s2 = new[] { 4, 5, 6 };
var result = s1.Zip(s2, (i1, i2) => new {Value1 = i1, Value2 = i2});
Se stai davvero utilizzando le matrici, il modo migliore è probabilmente solo quello di utilizzare il ciclo for
convenzionale con indici. Non è così carino, scontato, ma per quanto ne so .NET non offre un modo migliore per farlo.
Potresti anche incapsulare il tuo codice in un metodo chiamato zip
& # 8211; questa è una comune funzione di elenco di ordine superiore. Tuttavia, in C # manca un tipo di Tupla adatto, questo è piuttosto croccante. Si finisce per restituire un IEnumerable<KeyValuePair<T1, T2>>
che non è molto carino.
A proposito, stai davvero usando IEnumerable
invece di IEnumerable<T>
o perché lanci il valore Current
?
Usare IEnumerator per entrambi sarebbe carino
var currentValues = currentRow.Split(separatorChar);
using (IEnumerator<string> valueEnum = currentValues.GetEnumerator(), columnEnum = columnList.GetEnumerator()) {
while (valueEnum.MoveNext() && columnEnum.MoveNext())
valueMap.Add(columnEnum.Current, valueEnum.Current);
}
O crea un metodo di estensione
public static IEnumerable<TResult> Zip<T1, T2, TResult>(this IEnumerable<T1> source, IEnumerable<T2> other, Func<T1, T2, TResult> selector) {
using (IEnumerator<T1> sourceEnum = source.GetEnumerator()) {
using (IEnumerator<T2> otherEnum = other.GetEnumerator()) {
while (sourceEnum.MoveNext() && columnEnum.MoveNext())
yield return selector(sourceEnum.Current, otherEnum.Current);
}
}
}
Uso
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);
}
Invece di creare due array separati, potresti creare un array bidimensionale o un dizionario (che sarebbe meglio). Ma davvero, se funziona non proverei a cambiarlo.