C#で1つのForEachステートメントを使用して2つのリストまたは配列を反復する


142

これは一般的な知識のためだけです:

2つある場合、たとえば、Listと同じforeachループで両方を反復処理したい場合は、それを実行できますか?

編集する

明確にするために、私はこれをやりたかった:

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

しかし、foreach =)


10
ここで重要なのは「zip」です。
Mark Byers、

3
2つのリストを並行して反復ますか?または、最初のリストを繰り返し、次に他のリストを(単一のステートメントで)反復処理しますか?
Pavel Minaev 2009

あなたのやり方はzipよりも良いと思います
Alexander

回答:


272

これはZip操作と呼ばれ、.NET 4でサポートされます。

それで、あなたは次のようなものを書くことができるでしょう:

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

名前付きフィールドを持つ匿名型の代わりに、Tupleとその静的Tuple.Createヘルパーを使用して中括弧を節約することもできます。

foreach (var nw in numbers.Zip(words, Tuple.Create)) 
{
    Console.WriteLine(nw.Item1 + nw.Item2);
}


2
それらのZip操作について何も知りませんでした。そのトピックについて少し調査します。ありがとう!
ヒューゴ

4
@Hugo:関数型プログラミングの標準的な構成要素です:)
Mark Seemann '23

System.Linqを使用する必要もあります。
Jahmic

5
C#7以降、匿名型やTuple.Createの代わりにValueTuple(stackoverflow.com/a/45617748を参照)を使用することもできます。すなわちforeach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }
Erlend Graff、2018

14

.NET 4.0を待たない場合は、独自のZipメソッドを実装できます。以下は.NET 2.0で動作します。2つの列挙(またはリスト)の長さが異なる場合の処理​​方法に応じて、実装を調整できます。これは長い列挙の最後まで続き、短い列挙から欠落しているアイテムのデフォルト値を返します。

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

1
いい方法!:)。.NET 4 Zipメソッドmsdn.microsoft.com/en-us/library/dd267698.aspxと同じシグネチャを使用するようにいくつかの調整を行い、KVPの代わりにresultSelector(first、second)を返すことができます。
マルティン・コル

このメソッドは、問題になる可能性がある列挙子を破棄しないことに注意してください。たとえば、開いているファイルの行で列挙子を使用した場合などです。
Lii

11

UnionまたはConcatを使用できます。前者は重複を削除し、後者は削除しません

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

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

Unionの使用に関するもう1つの問題は、それらが等しいと評価された場合にインスタンスを破棄する可能性があることです。それは必ずしもあなたが望むものではないかもしれません。
マークシーマン

1
彼の意図は、同じタイプのコレクションを使用することだったと
思います

@Mark Seemann、私はすでに指摘しましたが、彼はConcatを使用することもできました
albertein

Unionと同様に、Concatは両方のリストが同じタイプである場合にのみ機能します。これがOPに必要なものかどうかは
わかり

これにより、すべての要素を含む新しいリストが作成されます。これはメモリの浪費です。代わりにLinq Concatを使用してください。
Drew Noakes、

3

これは、2つのリストを同時にループするために使用できるカスタムIEnumerable <>拡張メソッドです。

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

}

3

C#7以降、タプルを使用できます...

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

1
この状況で2つのリストの長さが同じでない場合はどうなりますか?
John August

では(x, y) => (x, y)、私たちは、名前の使用することができますtuple.xし、tuple.yエレガントです。したがって、2番目の形式も可能性があります(Num, Word) => (Num, Word)
ダッシュ

2
@JohnAugust短いシーケンスをたどった後に終了します。ドキュメントから: "シーケンスに同じ数の要素がない場合、メソッドはシーケンスの1つが終了するまでシーケンスをマージします。たとえば、1つのシーケンスに3つの要素があり、もう1つのシーケンスに4つの要素がある場合、結果のシーケンスは3つの要素しかありません。」
gregsmi

0

いいえ、そのためにforループを使用する必要があります。

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

次のようなことはできません

foreach (var objCurrent1 int lst1, var objCurrent2 in lst2)
{
    //...
}

カウントが異なる場合はどうなりますか?
Drew Noakes、

次に、列挙可能な任意のリストを受け入れるforeachも機能しないため、全体が役に立たなくなります。
Maximilian Mayerl、

0

対応する要素を持つ1つの要素が必要な場合は、

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

すべてのアイテムが2番目のリストの対応するアイテムと等しい場合、trueを返します

それがあなたの望むものであるがほとんどない場合、それはあなたがさらに詳しく説明した場合に役立ちます。


0

このメソッドはリスト実装で機能し、拡張メソッドとして実装できます。

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

0

リストが同じ長さであれば、ローカル整数変数を使用することもできます。

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

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

-1

次のこともできます。

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

注:の長さはlistAと同じでなければなりませんlistB


-3

私はリストが同じ長さであることを理解/希望します:いいえ、あなたの唯一の賭けは単純な古い標準のループを使用することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.