2008-10-28 11 views
17

Ich habe zwei Listen, die die gleiche Länge haben, ist es möglich, diese beiden Listen auf einmal zu durchlaufen?Schleife durch 2 Listen auf einmal

ich für die richtige Syntax suchen die unten

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

denken Sie, dies ist möglich in C# zu tun? Und wenn ja, was ist der Lambda-Ausdruck äquivalent?

+0

Möchten Sie die entsprechenden Elemente jeder Liste verketten? Oder willst du das kartesische Produkt der Listen? –

+0

@Vincent: Da er die gleiche Länge erwähnt, rate ich die erste. –

+0

Weder. Aber das sollte nicht wichtig sein, oder? – Graviton

Antwort

24

[bearbeiten]: zu klären; Dies ist nützlich im generischen LINQ/IEnumerable<T> Kontext, wo Sie kann nicht verwenden, einen Indexer, weil a: Es existiert nicht in einem Aufzählungszeichen, und b: Sie können nicht garantieren, dass Sie die Daten mehr als lesen können Einmal. Da das OP lambdas erwähnt, kommt es vor, dass LINQ nicht zu weit entfernt ist (und ja, ich weiß, dass LINQ und lambdas nicht ganz dasselbe sind).

Es klingt, als ob Sie den fehlenden Operator Zip benötigen; Sie können es fälschen:

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

Ich habe bearbeitet, um den Kontext hinzuzufügen ... –

+0

Schön, aber gibt es einen Grund, es zu einer Erweiterungsmethode zu machen? – peterchen

+0

es macht es ein bisschen mehr verfügbar - d. H. Hit "." und es scheint, im Gegensatz zu SomeUtilityClass.Zip (...) kennen zu müssen; es passt auch sehr gut mit den anderen Erweiterungsmethoden für IEnumerable , so dass ich mich damit als eine Erweiterungsmethode wohl fühle. –

11

Es wird viel einfacher sein, es zu tun nur in einem einfachen alten for-Schleife statt ...

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

Ja, keine Notwendigkeit, vier Zeilen Code für 13 zu vertauschen. –

+4

Der Unterschied ist, dass Sie nur die Zip-Erweiterung-Methode * einmal * benötigen und Sie können es für immer wiederverwenden. Außerdem funktioniert es in jeder Sequenz und nicht nur in Listen. –

+4

Ich wurde von Jon Skeet beschämt :( – jcelgin

5

Sie können es explizit tun.

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

Zumindest das (oder etwas Ähnliches) ist, was der Compiler für eine foreach-Schleife tut. Ich habe es jedoch nicht getestet, und ich schätze, einige Template-Parameter fehlen für die Enumeratoren.

Suchen Sie einfach GetEnumerator() von List und der IEnumerator-Schnittstelle.

+0

Sie haben MoveNext nicht auf ListBEnum verwendet - und Sie sollten auf jeden Fall "using" benutzen, schauen Sie sich vielleicht den Zip Ansatz an. –

+0

Das ist kein schlechter Ansatz, er muss nur das 'ListBenum.MoveNext' innerhalb der while-Schleife verschieben. Es ist auch allgemeiner als die Zip-Methode. – cdmckay

0

Senthil Kumar's tech blog, hat eine Reihe von Implementierungen (Python) itertools für C#, einschließlich itertools.izip abdeckt.

Von Itertools for C# - Cycle and Zip, haben Sie eine Lösung für eine beliebige Anzahl von Iterables (nicht nur Liste <T>). Man beachte, dass Zip eine Array bei jeder Iteration ergibt:

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

}

Der Code erhält Aufzählungen für alle Iterables, alle Enumeratoren vorwärts bewegt, sammelt sich die aktuellen Werte in ein Feld und liefert das Array. Dies geschieht so lange, bis einer der Enumeratoren keine Elemente mehr enthält.

+0

Nette Idee; zwei Gedanken zur Implementierung, allerdings. Erstens stellt er die Enumeratoren nicht zur Verfügung (hauptsächlich ein Problem für Ausnahmen) Noch wichtiger ist jedoch, dass die Wiederverwendung des Arrays riskant ist: Wenn ich .ToArray() /. ToList() hierauf anrufe, habe ich 30 Verweise auf * das gleiche Array * und keine Möglichkeit, die frühen Daten zu erhalten. –

+0

Ich stimme zu, aber beachten Sie, dass ein neues Array (könnte durch Liste oder eine andere IEnumerable ersetzt werden) für jeden Ertrag erstellt wird .. – gimel

+0

Oh richtig, mein Fehler, ich dachte, der T [] war außerhalb. Code Blindheit. –

1

Ich empfehle die Verwendung von einfachen alten for-Schleife, aber Sie sollten verschiedene Array-Längen berücksichtigen. So

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

kann in

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

oder sogar in

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

ich das gleiche Problem hatte, aber mit Listen von Objekten mit Listen von ihnen in .. für das, was ihren Wert , könnte dies jemandem mit dem gleichen Problem helfen.

Die Laufzeit ist nicht sehr gut, da IndexOf O (n) ist, aber zu der Zeit habe ich es mit viel mehr inner-foreach-Schleifen zu tun als in diesem Beispiel, also wollte ich nicht Umgang mit Iterator-Variablen.

Zu Zeiten wie diesem vermisse ich PHPs foreach ($ arrayList als $ key => $ value) Notation ... vielleicht fehlt mir etwas in C#, es muss eine Möglichkeit geben, den Index in O zu bekommen (c) Zeit! (leider diesen Beitrag sagt nein: Getting the array key in a 'foreach' loop)

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? } 
0

ich diese kleine Funktion haben, die mich durch diese beiden Listen Objekte iterieren hilft. Schema ist vom Typ SqlData, eine Klasse, die drei Eigenschaften enthält. Und Daten sind eine Liste, die Werte des dynamischen Typs enthält. Zuerst durchlaufe ich die Schemasammlung und verwende dann den Index des Elements, um das Datenobjekt zu durchlaufen.

public List<SqlData> SqlDataBinding(List<SqlData> schema, List<dynamic> data) 
{ 
    foreach (SqlData item in schema) 
    { 
     item.Values = data[schema.IndexOf(item)]; 
    } 
    return schema 
}