Iterieren Sie zwei Listen oder Arrays mit einer ForEach-Anweisung in C #

142

Dies nur für Allgemeinwissen:

Wenn ich zwei habe, sagen wir List , und ich möchte beide mit derselben foreach-Schleife iterieren, können wir das tun?

Bearbeiten

Um dies zu verdeutlichen, wollte ich Folgendes tun:

List<String> listA = new List<string> { "string", "string" };
List<String> listB = new List<string> { "string", "string" };

for(int i = 0; i < listA.Count; i++)
    listB[i] = listA[i];

Aber mit einem foreach =)

Hugo
quelle
10
Das wichtige Wort hier ist "zip".
Mark Byers
3
Möchten Sie zwei Listen parallel durchlaufen ? Oder möchten Sie zuerst eine Liste und dann die andere (mit einer einzelnen Anweisung) wiederholen?
Pavel Minaev
Ich denke, dein Weg sieht besser aus als zip
Alexander

Antworten:

271

Dies wird als Zip- Operation bezeichnet und in .NET 4 unterstützt.

Damit könnten Sie etwas schreiben wie:

var numbers = new [] { 1, 2, 3, 4 };
var words = new [] { "one", "two", "three", "four" };

var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w });
foreach(var nw in numbersAndWords)
{
    Console.WriteLine(nw.Number + nw.Word);
}

Alternativ zum anonymen Typ mit den benannten Feldern können Sie auch geschweifte Klammern verwenden, indem Sie ein Tupel und seinen statischen Tuple verwenden. Helfer erstellen:

foreach (var nw in numbers.Zip(words, Tuple.Create)) 
{
    Console.WriteLine(nw.Item1 + nw.Item2);
}
Mark Seemann
quelle
2
Hier ist ein Artikel darüber: community.bartdesmet.net/blogs/bart/archive/2008/11/03/…
James Kolpack
2
Wusste nichts über diese Zip-Operationen, ich werde eine kleine Recherche zu diesem Thema machen. Vielen Dank!
Hugo
4
@ Hugo: Es ist ein Standardkonstrukt in der funktionalen Programmierung :)
Mark Seemann
Sie müssen auch System.Linq verwenden.
Jahmic
5
Seit C # 7 können Sie anstelle anonymer Typen oder Tuple.Create auch ein ValueTuple (siehe stackoverflow.com/a/45617748 ) verwenden. Dh foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }.
Erlend Graff
14

Wenn Sie nicht auf .NET 4.0 warten möchten, können Sie Ihre eigene ZipMethode implementieren . Das Folgende funktioniert mit .NET 2.0. Sie können die Implementierung anpassen, je nachdem, wie Sie den Fall behandeln möchten, in dem die beiden Aufzählungen (oder Listen) unterschiedlich lang sind. Dieser wird bis zum Ende der längeren Aufzählung fortgesetzt und gibt die Standardwerte für fehlende Elemente aus der kürzeren Aufzählung zurück.

static IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> first, IEnumerable<U> second)
{
    IEnumerator<T> firstEnumerator = first.GetEnumerator();
    IEnumerator<U> secondEnumerator = second.GetEnumerator();

    while (firstEnumerator.MoveNext())
    {
        if (secondEnumerator.MoveNext())
        {
            yield return new KeyValuePair<T, U>(firstEnumerator.Current, secondEnumerator.Current);
        }
        else
        {
            yield return new KeyValuePair<T, U>(firstEnumerator.Current, default(U));
        }
    }
    while (secondEnumerator.MoveNext())
    {
        yield return new KeyValuePair<T, U>(default(T), secondEnumerator.Current);
    }
}

static void Test()
{
    IList<string> names = new string[] { "one", "two", "three" };
    IList<int> ids = new int[] { 1, 2, 3, 4 };

    foreach (KeyValuePair<string, int> keyValuePair in ParallelEnumerate(names, ids))
    {
        Console.WriteLine(keyValuePair.Key ?? "<null>" + " - " + keyValuePair.Value.ToString());
    }
}
Joe
quelle
1
Schöne Methode! :). Sie können einige Anpassungen vornehmen, um dieselbe Signatur wie die .NET 4-Zip-Methode msdn.microsoft.com/en-us/library/dd267698.aspx zu verwenden und resultSelector (erste, zweite) anstelle einer KVP zurückzugeben.
Martín Coll
Beachten Sie, dass diese Methode ihre Enumeratoren nicht entsorgt, was zu einem Problem werden kann, z. B. wenn sie mit Enumerables über die Zeilen geöffneter Dateien verwendet wird.
Lii
11

Sie können Union oder Concat verwenden. Ersteres entfernt Duplikate, letzteres nicht

foreach (var item in List1.Union(List1))
{
   //TODO: Real code goes here
}

foreach (var item in List1.Concat(List1))
{
   //TODO: Real code goes here
}
Albertein
quelle
Ein weiteres Problem bei der Verwendung einer Union besteht darin, dass Instanzen möglicherweise weggeworfen werden, wenn sie als gleich bewertet werden. Das ist vielleicht nicht immer das, was Sie wollen.
Mark Seemann
1
Ich habe es schwer, dass seine Absicht war, Sammlungen mit dem gleichen Typ zu verwenden,
Albertein
@ Mark Seemann, ich habe bereits darauf hingewiesen, dass er auch Concat
Albertein
Wie Union funktioniert Concat nur, wenn beide Listen vom gleichen Typ sind. Ich kann nicht sagen, ob dies das ist, was das OP braucht oder nicht ...
Mark Seemann
Dadurch wird eine neue Liste erstellt, die alle Elemente enthält. Dies ist eine Verschwendung von Speicher. Verwenden Sie stattdessen den Linq Concat.
Drew Noakes
3

Hier ist eine benutzerdefinierte IEnumerable <> -Erweiterungsmethode, mit der zwei Listen gleichzeitig durchlaufen werden können.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    public static class LinqCombinedSort
    {
        public static void Test()
        {
            var a = new[] {'a', 'b', 'c', 'd', 'e', 'f'};
            var b = new[] {3, 2, 1, 6, 5, 4};

            var sorted = from ab in a.Combine(b)
                         orderby ab.Second
                         select ab.First;

            foreach(char c in sorted)
            {
                Console.WriteLine(c);
            }
        }

        public static IEnumerable<Pair<TFirst, TSecond>> Combine<TFirst, TSecond>(this IEnumerable<TFirst> s1, IEnumerable<TSecond> s2)
        {
            using (var e1 = s1.GetEnumerator())
            using (var e2 = s2.GetEnumerator())
            {
                while (e1.MoveNext() && e2.MoveNext())
                {
                    yield return new Pair<TFirst, TSecond>(e1.Current, e2.Current);
                }
            }

        }


    }
    public class Pair<TFirst, TSecond>
    {
        private readonly TFirst _first;
        private readonly TSecond _second;
        private int _hashCode;

        public Pair(TFirst first, TSecond second)
        {
            _first = first;
            _second = second;
        }

        public TFirst First
        {
            get
            {
                return _first;
            }
        }

        public TSecond Second
        {
            get
            {
                return _second;
            }
        }

        public override int GetHashCode()
        {
            if (_hashCode == 0)
            {
                _hashCode = (ReferenceEquals(_first, null) ? 213 : _first.GetHashCode())*37 +
                            (ReferenceEquals(_second, null) ? 213 : _second.GetHashCode());
            }
            return _hashCode;
        }

        public override bool Equals(object obj)
        {
            var other = obj as Pair<TFirst, TSecond>;
            if (other == null)
            {
                return false;
            }
            return Equals(_first, other._first) && Equals(_second, other._second);
        }
    }

}
Samuel Neff
quelle
3

Seit C # 7 können Sie Tupel verwenden ...

int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };

foreach (var tuple in nums.Zip(words, (x, y) => (x, y)))
{
    Console.WriteLine($"{tuple.Item1}: {tuple.Item2}");
}

// or...
foreach (var tuple in nums.Zip(words, (x, y) => (Num: x, Word: y)))
{
    Console.WriteLine($"{tuple.Num}: {tuple.Word}");
}
Matěj Pokorný
quelle
1
Was passiert, wenn die beiden Listen in dieser Situation nicht gleich lang sind?
John August
Mit können (x, y) => (x, y)wir benannte tuple.xund tuple.yelegante verwenden. Die zweite Form könnte also auch sein(Num, Word) => (Num, Word)
schneidiger
2
@JohnAugust Wird beendet, nachdem die kürzere Sequenz durchlaufen wurde. Aus Dokumenten: "Wenn die Sequenzen nicht die gleiche Anzahl von Elementen haben, führt die Methode Sequenzen zusammen, bis sie das Ende eines von ihnen erreichen. Wenn beispielsweise eine Sequenz drei Elemente und die andere vier Elemente enthält, wird die Ergebnissequenz habe nur drei Elemente. "
Gregsmi
0

Nein, dafür müssten Sie eine for-Schleife verwenden.

for (int i = 0; i < lst1.Count; i++)
{
    //lst1[i]...
    //lst2[i]...
}

Sie können so etwas nicht tun

foreach (var objCurrent1 int lst1, var objCurrent2 in lst2)
{
    //...
}
Maximilian Mayerl
quelle
Was ist, wenn sie unterschiedliche Zählungen haben?
Drew Noakes
Dann würde ein foreach, der eine beliebige Liste von Aufzählungen akzeptieren würde, nicht so gut funktionieren, was das Ganze unbrauchbar macht.
Maximilian Mayerl
0

Wenn Sie ein Element mit dem entsprechenden möchten, können Sie dies tun

Enumerable.Range(0, List1.Count).All(x => List1[x] == List2[x]);

Dies gibt true zurück, wenn jedes Element dem entsprechenden Element in der zweiten Liste entspricht

Wenn das fast, aber nicht ganz das ist, was Sie wollen, würde es helfen, wenn Sie mehr ausarbeiten würden.

Albertein
quelle
0

Diese Methode würde für eine Listenimplementierung funktionieren und könnte als Erweiterungsmethode implementiert werden.

public void TestMethod()
{
    var first = new List<int> {1, 2, 3, 4, 5};
    var second = new List<string> {"One", "Two", "Three", "Four", "Five"};

    foreach(var value in this.Zip(first, second, (x, y) => new {Number = x, Text = y}))
    {
        Console.WriteLine("{0} - {1}",value.Number, value.Text);
    }
}

public IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(List<TFirst> first, List<TSecond> second, Func<TFirst, TSecond, TResult> selector)
{
    if (first.Count != second.Count)
        throw new Exception();  

    for(var i = 0; i < first.Count; i++)
    {
        yield return selector.Invoke(first[i], second[i]);
    }
}
Rohan West
quelle
0

Sie können auch einfach eine lokale Ganzzahlvariable verwenden, wenn die Listen dieselbe Länge haben:

List<classA> listA = fillListA();
List<classB> listB = fillListB();

var i = 0;
foreach(var itemA in listA)
{
    Console.WriteLine(itemA  + listB[i++]);
}
Thalm
quelle
-1

Sie können auch Folgendes tun:

var i = 0;
foreach (var itemA in listA)
{
  Console.WriteLine(itemA + listB[i++]);
}

Hinweis: Die Länge von listAmuss mit gleich sein listB.

Rigel1121
quelle
-3

Ich verstehe / hoffe, dass die Listen die gleiche Länge haben: Nein, Ihre einzige Wette besteht darin, einen einfachen alten Standard für die Schleife zu verwenden.

Benjamin Podszun
quelle