[C#] Linq에서 Enumerable.Zip 확장 방법은 무엇입니까?

Enumerable.ZipLinq에서 확장 방법을 사용하는 것은 무엇입니까 ?



답변

Zip 연산자는 지정된 선택기 함수를 사용하여 두 시퀀스의 해당 요소를 병합합니다.

var letters= new string[] { "A", "B", "C", "D", "E" };
var numbers= new int[] { 1, 2, 3 };
var q = letters.Zip(numbers, (l, n) => l + n.ToString());
foreach (var s in q)
    Console.WriteLine(s);

출력

A1
B2
C3


답변

Zip두 시퀀스를 하나로 결합하기위한 것입니다. 예를 들어 시퀀스가있는 경우

1, 2, 3

10, 20, 30

각 시퀀스에서 동일한 위치에 요소를 곱한 결과 인 시퀀스를 얻으려면

10, 40, 90

넌 말할 수있다

var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);

하나의 시퀀스를 지퍼의 왼쪽으로, 다른 시퀀스를 지퍼의 오른쪽으로 생각하기 때문에 “zip”이라고하며, 지퍼 연산자는 양측을 함께 치아에서 떼어냅니다. 순서의 요소).


답변

두 시퀀스를 반복하고 해당 요소를 하나씩 새 단일 시퀀스로 결합합니다. 따라서 시퀀스 A의 요소를 가져 와서 시퀀스 B의 해당 요소로 변환하면 결과가 시퀀스 C의 요소를 형성합니다.

생각하는 한 가지 방법 Select은 단일 컬렉션에서 항목을 변환하는 대신 한 번에 두 컬렉션에서 작동한다는 점을 제외하고 와 비슷하다는 것 입니다.

방법에 대한 MSDN 기사에서 :

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

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

foreach (var item in numbersAndWords)
    Console.WriteLine(item);

// This code produces the following output:

// 1 one
// 2 two
// 3 three

명령형 코드 로이 작업을 수행했다면 아마도 다음과 같이 할 것입니다.

for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
    numbersAndWords.Add(numbers[i] + " " + words[i]);
}

또는 LINQ에없는 경우 Zip다음을 수행 할 수 있습니다.

var numbersAndWords = numbers.Select(
                          (num, i) => num + " " + words[i]
                      );

이는 데이터가 각각 길이와 순서가 같고 동일한 객체 집합의 다른 속성을 설명하는 간단한 배열과 같은 목록으로 분산 된 경우에 유용합니다. Zip이러한 데이터 조각을보다 일관된 구조로 묶을 수 있습니다.

따라서 상태 이름 배열과 약어의 또 다른 배열이 있으면 다음 State과 같이 클래스 로 조합 할 수 있습니다 .

IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
    return stateNames.Zip(statePopulations,
                          (name, population) => new State()
                          {
                              Name = name,
                              Population = population
                          });
}


답변

이름이 Zip당신을 버리지 못하게하십시오 . 파일이나 폴더를 압축하는 것과 같이 압축하는 것과는 아무런 관련이 없습니다 (압축). 그것은 실제로 옷의 지퍼가 작동하는 방식에서 이름을 얻습니다. 옷의 지퍼는 2면이 있고 각면에는 많은 이가 있습니다. 한 방향으로 갈 때 지퍼는 양쪽을 열거하고 (이동) 치아를 꽉 쥐어 지퍼를 닫습니다. 다른 방향으로 가면 이가 열립니다. 열려 있거나 닫힌 지퍼로 끝납니다.

Zip방법 과 동일한 아이디어입니다 . 두 개의 컬렉션이있는 예를 고려하십시오. 하나는 편지를, 다른 하나는 그 편지로 시작하는 음식 품목의 이름을 보유합니다. 명확성을 위해 내가 그들을 호출하고 leftSideOfZipperrightSideOfZipper. 코드는 다음과 같습니다.

var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };

우리의 임무는 과일의 문자 :와 이름으로 구분되는 하나의 컬렉션을 만드는 것입니다 . 이처럼 :

A : Apple
B : Banana
C : Coconut
D : Donut

Zip구조에. 우리의 지퍼 용어를 따라하기 위해 우리는이 결과 closedZipper를 호출 할 것이고 왼쪽 지퍼의 항목과 우리가 호출 leftTooth할 오른쪽 righTooth은 명백한 이유입니다.

var closedZipper = leftSideOfZipper
   .Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();

위에서 우리는 지퍼의 왼쪽과 지퍼의 오른쪽을 열거하고 (이동) 각 치아에서 작업을 수행합니다. 우리가 수행하는 작업은 왼쪽 치아 (식품 편지)와 :오른쪽 치아 (식품 이름)를 연결하는 것입니다. 우리는이 코드를 사용하여 그렇게합니다 :

(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)

최종 결과는 다음과 같습니다.

A : Apple
B : Banana
C : Coconut
D : Donut

마지막 문자 E는 어떻게 되었습니까?

실제 옷 지퍼와 한쪽을 열거 (풀링)하는 경우 왼쪽이나 오른쪽이 중요하지 않고 다른 쪽보다 치아가 적 으면 어떻게됩니까? 지퍼는 거기서 멈출 것이다. 이 Zip방법은 정확히 동일하게 수행됩니다. 한 쪽의 마지막 항목에 도달하면 중지됩니다. 우리의 경우 오른쪽에는 이빨 (식품 이름)이 적으므로 “도넛”에서 멈 춥니 다.


답변

의견 섹션에 게시 할 담당자가 없지만 관련 질문에 답변해야합니다.

하나의 목록에서 요소가 부족한 곳에서 zip을 계속 사용하려면 어떻게해야합니까? 이 경우 더 짧은 목록 요소가 기본값을 가져야합니다. 이 경우 출력은 A1, B2, C3, D0, E0이됩니다. – liang 11 월 19 일 15시 3:29

할 일은 Array.Resize ()를 사용하여 더 짧은 시퀀스를 기본값으로 채운 다음 Zip ()을 함께 사용하는 것입니다.

코드 예 :

var letters = new string[] { "A", "B", "C", "D", "E" };
var numbers = new int[] { 1, 2, 3 };
if (numbers.Length < letters.Length)
    Array.Resize(ref numbers, letters.Length);
var q = letters.Zip(numbers, (l, n) => l + n.ToString());
foreach (var s in q)
    Console.WriteLine(s);

산출:

A1
B2
C3
D0
E0

Array.Resize () 를 사용하면주의 사항이 있습니다 : Redim Preserve in C #?

어떤 시퀀스가 ​​더 짧은 시퀀스인지 알 수없는 경우이를 일시 중단하는 함수를 만들 수 있습니다.

static void Main(string[] args)
{
    var letters = new string[] { "A", "B", "C", "D", "E" };
    var numbers = new int[] { 1, 2, 3 };
    var q = letters.Zip(numbers, (l, n) => l + n.ToString()).ToArray();
    var qDef = ZipDefault(letters, numbers);
    Array.Resize(ref q, qDef.Count());
    // Note: using a second .Zip() to show the results side-by-side
    foreach (var s in q.Zip(qDef, (a, b) => string.Format("{0, 2} {1, 2}", a, b)))
        Console.WriteLine(s);
}

static IEnumerable<string> ZipDefault(string[] letters, int[] numbers)
{
    switch (letters.Length.CompareTo(numbers.Length))
    {
        case -1: Array.Resize(ref letters, numbers.Length); break;
        case 0: goto default;
        case 1: Array.Resize(ref numbers, letters.Length); break;
        default: break;
    }
    return letters.Zip(numbers, (l, n) => l + n.ToString());
}

ZipDefault ()와 함께 일반 .Zip () 출력 :

A1 A1
B2 B2
C3 C3
   D0
   E0

원래의 질문에 대한 주된 대답으로 돌아가서 , 또 다른 흥미로운 일을 원할 수도 있습니다 ( “압축”될 시퀀스의 길이가 다른 경우) 목록 의 과 같은 방식으로 그것들을 결합하는 것입니다 상단 대신 일치합니다. 이것은 .Skip ()를 사용하여 적절한 수의 항목을 “건너 뛰기”하여 수행 할 수 있습니다.

foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);

산출:

C1
D2
E3


답변

여기에 많은 답변이 Zip있지만, 실제 사용 사례를 설명하지 않고의 사용에 동기를 부여합니다 Zip.

Zip일련의 사물을 반복 하는 데 환상적인 하나의 특히 일반적인 패턴입니다 . 이것은 열거 형 X자체 를 반복하고 1 요소를 생략 하여 수행됩니다 x.Zip(x.Skip(1). 시각적 예 :

 x | x.Skip(1) | x.Zip(x.Skip(1), ...)
---+-----------+----------------------
   |    1      |
 1 |    2      | (1, 2)
 2 |    3      | (2, 1)
 3 |    4      | (3, 2)
 4 |    5      | (4, 3)

이 연속 쌍은 값 사이의 첫 번째 차이점을 찾는 데 유용합니다. 예를 들어, 연속 쌍을 IEnumable<MouseXPosition>사용하여을 생성 할 수 있습니다 IEnumerable<MouseXDelta>. 마찬가지로, a의 샘플링 된 bool값은 / / / button와 같은 이벤트로 해석 될 수 있습니다 . 그런 다음 해당 이벤트는 메소드를 위임하기위한 호출을 유발할 수 있습니다. 예를 들면 다음과 같습니다.NotPressedClickedHeldReleased

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

enum MouseEvent { NotPressed, Clicked, Held, Released }

public class Program {
    public static void Main() {
        // Example: Sampling the boolean state of a mouse button
        List<bool> mouseStates = new List<bool> { false, false, false, false, true, true, true, false, true, false, false, true };

        mouseStates.Zip(mouseStates.Skip(1), (oldMouseState, newMouseState) => {
            if (oldMouseState) {
                if (newMouseState) return MouseEvent.Held;
                else return MouseEvent.Released;
            } else {
                if (newMouseState) return MouseEvent.Clicked;
                else return MouseEvent.NotPressed;
            }
        })
        .ToList()
        .ForEach(mouseEvent => Console.WriteLine(mouseEvent) );
    }
}

인쇄물:

NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked


답변

다른 사람들이 언급했듯이 Zip을 사용하면 추가 Linq 문 또는 foreach 루프에 사용하기 위해 두 컬렉션을 결합 할 수 있습니다.

for 루프와 두 개의 배열이 필요한 작업은 이제 익명 객체를 사용하여 foreach 루프에서 수행 할 수 있습니다.

방금 발견 한 예는 어리석은 일이지만 병렬 처리가 유익한 경우 부작용이있는 단일 행 큐 순회가 유용 할 수 있습니다.

timeSegments
    .Zip(timeSegments.Skip(1), (Current, Next) => new {Current, Next})
    .Where(zip => zip.Current.EndTime > zip.Next.StartTime)
    .AsParallel()
    .ForAll(zip => zip.Current.EndTime = zip.Next.StartTime);

timeSegments는 대기열에서 현재 또는 대기열에서 제외 된 항목을 나타냅니다 (마지막 요소는 Zip으로 잘립니다). timeSegments.Skip (1)은 큐에서 다음 또는 엿보기 항목을 나타냅니다. Zip 메서드는이 두 속성을 Next 및 Current 속성을 사용하여 단일 익명 개체로 결합합니다. 그런 다음 Where로 필터링하고 AsParallel (). ForAll을 사용하여 변경합니다. 물론 마지막 비트는 규칙적인 foreach 또는 문제가되는 시간 세그먼트를 반환하는 다른 Select 문일 수 있습니다.