LinqでのEnumerable.Zip拡張メソッドの使用は何ですか?


回答:


191

Zip演算子は、指定されたセレクター関数を使用して、2つのシーケンスの対​​応する要素をマージします。

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


2
1つのリストで要素が不足した場合にzipを続行するにはどうすればよいですか?その場合、より短いリスト要素はデフォルト値をとるべきです。この場合の出力は、A1、B2、C3、D0、E0です。
liang 2015年

2
@liang 2つの選択肢:A)独自のZip代替案を作成します。B)にメソッドを書くyield return短いリストの各要素、その後、継続yield returnするdefault無期限以降。(オプションBでは、どちらのリストが短いかを事前に知っておく必要があります。)
jpaugh

105

Zip2つのシーケンスを1つに結合するためのものです。たとえば、シーケンスがある場合

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

1つのシーケンスをジッパーの左側と見なし、もう1つのシーケンスをジッパーの右側と見なすため、「zip」と呼ばれます。zipオペレーターは、2つの側を一緒に引っ張って歯を離します(シーケンスの要素)


8
間違いなくここで最高の説明。
Maxim Gershkovich、2012年

2
ジッパーの例が大好きでした。それはとても自然なことでした。私の最初の印象は、それが速度に関係しているのか、それとも、あなたが車で通りを通り抜けるかのようなものだと思いました。
RBT 2016

23

2つのシーケンスを反復処理し、それらの要素を1つずつ組み合わせて、1つの新しいシーケンスにします。したがって、シーケンスAの要素を取得し、シーケンスBの対応する要素で変換すると、結果はシーケンスCの要素になります。

これについて考える1つの方法はSelect、単一のコレクションからアイテムを変換するのではなく、2つのコレクションを同時に処理することを除いて、と同様です。

メソッドに関する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方法も同じです。2つのコレクションがある例を考えてみましょう。1つは文字を保持し、もう1つはその文字で始まる食品の名前を保持します。明確にするために、私はそれらをleftSideOfZipperと呼びますrightSideOfZipper。これがコードです。

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

私たちの仕事は、果物の文字がa :とその名前で区切られた1つのコレクションを作成することです。このような:

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

Zip救助へ。ジッパーの用語についていくためclosedZipperに、この結果と呼ぶ左側のジッパーの項目leftToothと右側のrighTooth理由を明らかにします。

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

上記では、ジッパーの左側とジッパーの右側を列挙(移動)し、各歯に対して操作を実行しています。私たちが実行している操作は、左の歯(食品の文字)をaで連結し、:次に右の歯(食品の名前)を連結することです。これは次のコードを使用して行います。

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

最終結果はこれです:

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

最後の文字Eはどうなりましたか?

実際の服のジッパーと片側を数えている(引っ張っている)場合、左側または右側は問題ではなく、反対側より歯数が少ないとどうなりますか?まあジッパーはそこで止まります。Zipこの方法は、正確に同じことをするでしょう:それはどちらかの側に最後の項目に達するとそれは停止します。私たちの場合、右側の歯(食品名)が少ないため、「Donut」で停止します。


1
+1。はい、「Zip」という名前は最初は混乱する可能性があります。おそらく、「インターリーブ」または「ウィーブ」は、このメソッドのよりわかりやすい名前でした。
BACON、2017年

1
@baconはい、でも私はジッパーの例を使うことができなかったでしょう;)ジッパーのようなものを理解したら、それはその後かなり簡単です。
CodingYoshi 2017年

私はZip拡張メソッドが何をするかを正確に知っていましたが、なぜそれがそのように名付けられたのかについて常に興味を持ちました。ソフトウェアの一般的な専門用語では、zipは常に別の意味を持っています。素晴らしいアナロジー:-)あなたはクリエーターの心を読んだに違いありません。
Raghu Reddy Muttana、2018

7

私はコメントセクションに投稿する担当者ポイントがありませんが、関連する質問に答えるために:

1つのリストで要素が不足した場合にzipを続行するにはどうすればよいですか?その場合、より短いリスト要素はデフォルト値をとるべきです。この場合の出力は、A1、B2、C3、D0、E0です。– liang 2015年11月19日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 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

元の質問の主な回答に戻ると、(「圧縮」されるシーケンスの長さが異なる場合に)実行したい可能性のあるもう1つの興味深いことは、リストの最後になるようにそれらを結合することです。トップの代わりに一致します。これは、.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; } }
ページフォールト

コメント内のコードを
適切に

7

ここでの答えの多くはZipを示していますが、の使用を動機づける実際のユースケースを実際に説明することはありませんZip

Zip連続する2つのペアを反復処理するのに最適な、特に一般的なパターンの1つ。これは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>。同様に、boolaのサンプル値は、/ / / 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では、2つのコレクションを組み合わせて、さらなるLinqステートメントまたはforeachループで使用できます。

以前はforループと2つの配列を必要としていた操作を、匿名オブジェクトを使用して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メソッドは、これら2つを組み合わせて、NextプロパティとCurrentプロパティを持つ単一の匿名オブジェクトにします。次に、Whereでフィルタリングし、AsParallel()。ForAllで変更を加えます。もちろん、最後のビットは、通常のforeachまたは問題のある時間セグメントを返す別のSelectステートメントにすぎません。


3

Zipメソッドを使用すると、呼び出し元であるマージ関数プロバイダーを使用して、2つの無関係なシーケンスを「マージ」できます。MSDNの例は、実際にはZipで何ができるかを示すのにかなり適しています。この例では、2つの任意の無関係なシーケンスを取り、それらを任意の関数を使用して結合します(この場合、両方のシーケンスのアイテムを1つの文字列に連結するだけです)。

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.