Enumerable.Zip
Linq の拡張メソッドの使用は何ですか?
Enumerable.Zip
Linq の拡張メソッドの使用は何ですか?
回答:
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
Zip
代替案を作成します。B)にメソッドを書くyield return
短いリストの各要素、その後、継続yield return
するdefault
無期限以降。(オプションBでは、どちらのリストが短いかを事前に知っておく必要があります。)
Zip
2つのシーケンスを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つの側を一緒に引っ張って歯を離します(シーケンスの要素)
2つのシーケンスを反復処理し、それらの要素を1つずつ組み合わせて、1つの新しいシーケンスにします。したがって、シーケンスAの要素を取得し、シーケンスBの対応する要素で変換すると、結果はシーケンスCの要素になります。
これについて考える1つの方法はSelect
、単一のコレクションからアイテムを変換するのではなく、2つのコレクションを同時に処理することを除いて、と同様です。
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
名前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つのリストで要素が不足した場合に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; } }
ここでの答えの多くは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>
。同様に、bool
aのサンプル値は、/ / / button
などのイベントに解釈できます。これらのイベントは、デリゲートメソッドへの呼び出しを駆動できます。次に例を示します。NotPressed
Clicked
Held
Released
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では、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ステートメントにすぎません。
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
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