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?

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top