Frage

Ich habe zwei Arrays gebaut, während eine Textdatei Parsing. Die erste enthält die Spaltennamen, die zweite, die Werte von der aktuellen Zeile enthält. Ich brauche beiden Listen iterieren auf einmal eine Karte zu bauen. Im Moment habe ich die folgenden:

var currentValues = currentRow.Split(separatorChar);
var valueEnumerator = currentValues.GetEnumerator();

foreach (String column in columnList)
{
    valueEnumerator.MoveNext();
    valueMap.Add(column, (String)valueEnumerator.Current);
}

Das funktioniert ganz gut, aber es ist nicht ganz mein Gefühl von Eleganz zu erfüllen, und es wird wirklich haarig, wenn die Anzahl von Arrays größer als zwei (wie ich gelegentlich zu tun haben). Hat jemand eine andere, knappere Idiom haben?

War es hilfreich?

Lösung

, wenn es die gleiche Anzahl von Spaltennamen, da es Elemente in jeder Reihe, könnten Sie eine nicht-Schleife verwenden?

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
}

Andere Tipps

Sie haben einen nicht-offensichtlichen pseudo-Fehler in Ihrem ursprünglichen Code bekommen - IEnumerator<T> erstreckt IDisposable, damit Sie es entsorgt werden. Dies kann mit Iterator Blöcke sehr wichtig sein! Kein Problem für Arrays, wäre aber mit anderen IEnumerable<T> Implementierungen.

ich es so tun würde:

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");
        }
    }        
}

Dann können Sie diese wiederverwenden, wenn Sie das Bedürfnis haben:

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

Alternativ können Sie eine generische Paar Art erstellen, und in der PairUp Verfahren loswerden des Projektionsparameter erhalten.

EDIT:

Mit dem Pair-Typ, der aufrufende Code würde wie folgt aussehen:

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

Das sieht etwa so einfach, wie Sie bekommen können. Ja, Sie müssen irgendwo die Dienstprogramm Methode setzen, als wieder verwendbarer Code. Kaum ein Problem aus meiner Sicht. Jetzt für mehrere Arrays ...

Wenn die Arrays von unterschiedlichen Typen sind, haben wir ein Problem. Sie können eine beliebige Anzahl von Typparametern in einer generischen Methode / Typdeklaration auszudrücken - Sie Versionen von PairUp für so viele Parameter des Typs schreiben konnte, wie man wollte, so wie es Action und Func Delegierten für bis zu 4 Delegat Parameter - aber Sie können nicht willkürlich machen.

Wenn die Werte alle vom gleichen Typ sein, aber - und wenn Sie glücklich sind, um Arrays zu bleiben - es ist einfach. (Non-Arrays ist auch in Ordnung, aber man kann die Länge nicht vor der Zeit zu überprüfen.) Sie können dies tun:

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;
    }
}

Dann würde der Angerufene-Code sein:

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

Dies beinhaltet eine bestimmte Menge des Kopierens, natürlich - Sie jedes Mal eine Reihe sind zu schaffen. Sie könnten das ändern, indem eine andere Art, wie diese Einführung:

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];
    }
}

Dies würde wahrscheinlich als übertrieben von den meisten obwohl angesehen werden;)

Ich kann halten mit allen Arten von Ideen kommen, um ehrlich zu sein ... aber die Grundlagen sind:

  • Mit einem wenig von wieder verwendbarer Arbeit, können Sie die Telefonvorwahl schöner
  • machen
  • Für beliebige Kombinationen von Arten werden müssen Sie jede Anzahl von Parametern tun (2, 3, 4 ...) separat aufgrund der Art und Weise Generika funktioniert
  • Wenn Sie glücklich sind, für jeden Teil des gleichen Typs zu verwenden, können Sie es besser machen

In einer funktionalen Sprache würden Sie in der Regel eine „zip“ Funktion finden, die hoffentlich Teil eines C # 4.0 sein wird. Bart de Smet eine lustige Implementierung von Reißverschlusses bietet auf Basis bestehender LINQ Funktionen:

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));
}

Dann können Sie tun:

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

Wenn Sie wirklich Arrays verwendet wird, ist der beste Weg, wahrscheinlich nur mit der Indizes die herkömmliche for Schleife zu verwenden. Nicht so schön, gewährt, aber soweit ich weiß, .NET bietet keine bessere Art und Weise, dies zu tun.

Sie können auch Ihren Code in eine Methode namens zip kapseln - dies ist eine gemeinsame höhere Ordnung Listenfunktion. Um jedoch einen geeigneten Tuple Typen C # fehlt, das ist ganz crufty. Sie würden eine IEnumerable<KeyValuePair<T1, T2>> am Ende der Rückkehr, die nicht sehr schön ist.

By the way, sind Sie wirklich mit IEnumerable statt IEnumerable<T> oder warum werfen Sie den Current Wert?

Verwenden Sie IEnumerator für beide wäre schön

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);
}

oder erstellen Sie eine Erweiterungsmethoden

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);
        }
    }
}

Verwendung

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);
}

Statt zwei getrennte Arrays schaffen könnten Sie ein zweidimensionales Array machen, oder ein Wörterbuch (das wäre besser). Aber wirklich, wenn es funktioniert würde ich nicht versuchen, es zu ändern.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top