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


답변:


191

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

41
나는이 대답처럼 요소의 수는 유사 일치하지 않을 때 어떻게되는지 보여주기 때문에 MSDN 설명서
DLeh

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

2
@liang 두 가지 선택 : A) 자신의 Zip대안을 작성하십시오 . B) yield return짧은 목록의 각 요소에 방법을 쓴 다음 그 후에 무한정 계속 yield return합니다 default. (옵션 B는 어느리스트가 더 짧은 지 미리 알아야합니다.)
jpaugh

105

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"이라고하며, 지퍼 연산자는 양측을 함께 치아에서 떼어냅니다. 순서의 요소).


8
여기서 가장 좋은 설명입니다.
Maxim Gershkovich

2
지퍼의 예를 좋아했습니다. 너무 자연 스럽습니다. 나의 첫인상은 마치 자동차의 거리를 질주하는 것처럼 속도 나 그와 비슷한 것이 있다는 것입니다.
RBT

23

두 시퀀스를 반복하고 해당 요소를 하나씩 새 단일 시퀀스로 결합합니다. 따라서 시퀀스 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
                          });
}

이 답변도 마음에 들었습니다.Select
iliketocode

17

이름이 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방법은 정확히 동일하게 수행됩니다. 한 쪽의 마지막 항목에 도달하면 중지됩니다. 우리의 경우 오른쪽에는 이빨 (식품 이름)이 적으므로 "도넛"에서 멈 춥니 다.


1
+1. 네, "우편"이라는 이름은 처음에는 혼란 스러울 수 있습니다. 아마도 "Interleave"또는 "Weave"는이 방법에 대한보다 구체적인 이름 일 것입니다.
BACON

1
@bacon 예.하지만 지퍼 예제를 사용할 수 없었습니다.) 지퍼와 같은 것을 알아 낸 후에는 꽤 똑바로 진행됩니다.
CodingYoshi

Zip 확장 방법이 정확히 무엇인지 알고 있었지만 왜 그렇게 이름이 붙여 졌는지 항상 궁금했습니다. 소프트웨어 전문 용어에서 zip은 항상 다른 것을 의미했습니다. 위대한 비유 :-) 창조자의 마음을 읽었을 것입니다.
Raghu Reddy Muttana

7

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

하나의 목록에서 요소가 부족한 곳에서 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

특히 컬렉션 중 하나가 큰 경우 크기 조정은 낭비입니다. 실제로 원하는 것은 컬렉션이 끝난 후에도 계속 열거되어 필요에 따라 빈 컬렉션으로 채워지는 열거입니다. : 당신과 함께 그렇게 할 수 public static IEnumerable<T> Pad<T>(this IEnumerable<T> input, long minLength, T value = default(T)) { long numYielded = 0; foreach (T element in input) { yield return element; ++numYielded; } while (numYielded < minLength) { yield return value; ++numYielded; } }
페이지 부재

주석에 코드를 성공적으로 포맷하는 방법을 잘 모르겠습니다 ...
Pagefault

7

여기에 많은 답변이 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

6

다른 사람들이 언급했듯이 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 문일 수 있습니다.


3

Zip 방법을 사용하면 호출자 인 병합 함수 공급자를 사용하여 두 개의 관련되지 않은 시퀀스를 "병합"할 수 있습니다. MSDN의 예제는 실제로 Zip으로 수행 할 수있는 작업을 시연하는 데 능숙합니다. 이 예제에서는 관련없는 두 개의 임의 시퀀스를 가져 와서 임의의 함수를 사용하여 결합합니다 (이 경우 두 시퀀스의 항목을 단일 문자열로 연결).

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

0
string[] fname = { "mark", "john", "joseph" };
string[] lname = { "castro", "cruz", "lopez" };

var fullName = fname.Zip(lname, (f, l) => f + " " + l);

foreach (var item in fullName)
{
    Console.WriteLine(item);
}
// The output are

//mark castro..etc
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.