C #에서 하나의 ForEach 문으로 두 개의 목록 또는 배열을 반복하십시오.


142

이것은 일반적인 지식을 위해서만 :

두 개가있는 경우 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
두 개의 목록 을 병렬 로 반복 하시겠습니까? 아니면 첫 번째 목록을 반복 한 다음 다른 목록을 반복하고 싶습니까?
Pavel Minaev

나는 우편보다 더 길의 모습을 생각
알렉산더

답변:


274

이것을 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을 사용하여 중괄호를 절약 할 수도 있습니다.

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


2
Zip 작업에 대해 전혀 몰랐습니다. 그 주제에 대해 약간의 연구를하겠습니다. 감사!
Hugo

4
@ Hugo : 함수형 프로그래밍의 표준 구조입니다 :)
Mark Seemann

또한 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

14

.NET 4.0을 기다리지 않으려면 고유 한 Zip방법을 구현할 수 있습니다 . 다음은 .NET 2.0에서 작동합니다. 두 열거 형 (또는 목록)의 길이가 다른 경우 처리 방법에 따라 구현을 조정할 수 있습니다. 이것은 더 긴 열거의 끝까지 계속되고, 더 짧은 열거에서 누락 된 항목의 기본값을 반환합니다.

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)를 반환 하도록 약간 조정할 수 있습니다 .
Martín Coll

이 방법은 열거자가 문제가 될 수있는 열거자를 폐기하지 않습니다 (예 : 열린 파일 행에서 열거 가능 항목과 함께 사용하는 경우).
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을 사용할 때의 또 다른 문제는 인스턴스가 동일한 것으로 평가되면 인스턴스를 버릴 수 있다는 것입니다. 항상 원하는 것은 아닙니다.
Mark Seemann

1
그의 의도는 같은 유형의 컬렉션을 사용하는 것이 었습니다.
albertein

@ Mark Seemann, 나는 이미 지적했다, 그는 또한 Concat을 사용할 수 있음
albertein

Union과 마찬가지로 Concat은 두 목록이 동일한 유형 인 경우에만 작동합니다. 이것이 OP가 필요로하는 것인지 아닌지를 말할 수 없다 ...
Mark Seemann

모든 요소가 포함 된 새 목록이 생성됩니다. 이것은 메모리 낭비입니다. 대신 Linq Concat을 사용하십시오.
Drew Noakes

3

다음은 두 목록을 동시에 반복하는 데 사용할 수있는 사용자 지정 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부터 Tuples를 사용할 수 있습니다 ...

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
이 상황에서 두 목록의 길이가 같지 않으면 어떻게됩니까?
John August

(x, y) => (x, y)이름 tuple.x과 함께 사용할 수 있습니다 tuple.y. 그래서 두 번째 형태는 수(Num, Word) => (Num, Word)
dashesy

2
@JohnAugust 더 짧은 시퀀스를 통과 한 후에 종료됩니다. docs : "시퀀스에 동일한 개수의 요소가없는 경우이 메서드는 시퀀스 중 하나의 끝에 도달 할 때까지 시퀀스를 병합합니다. 예를 들어 한 시퀀스에 3 개의 요소가 있고 다른 하나에 4 개의 요소가 있으면 결과 시퀀스는 세 가지 요소 만 있습니다. "
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

해당 요소와 하나의 요소를 원한다면 할 수 있습니다

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

모든 항목이 두 번째 목록의 해당 항목과 같으면 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.