Linq'te Enumerable.Zip uzatma yönteminin kullanımı nedir?


Yanıtlar:


192

Zip operatörü, belirtilen bir seçici işlevini kullanarak iki dizinin karşılık gelen öğelerini birleştirir.

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

Çıkışı

A1
B2
C3

42
Bu yanıtı beğendim çünkü öğelerin sayısı eşleşmediğinde, msdn belgelerine
DLeh

2
ya zip'in bir listenin öğeleri kalmadığı durumda devam etmesini istersem? bu durumda, daha kısa liste öğesi varsayılan değeri almalıdır. Bu durumda çıktı A1, B2, C3, D0, E0 olacaktır.
liang

2
@liang İki seçenek: A) Kendi Zipalternatifinizi yazın . B) bir yöntem yazın yield returndaha kısa listenin her bir elemanına ve ardından devam yield returning defaultsüresiz bundan sonra. (B Seçeneği, hangi listenin daha kısa olduğunu önceden bilmenizi gerektirir.)
jpaugh

105

Zipiki diziyi tek bir dizide birleştirmek içindir. Örneğin, dizileriniz varsa

1, 2, 3

ve

10, 20, 30

ve her dizide aynı konumdaki öğelerin çarpılmasının sonucu olan dizinin

10, 40, 90

söyleyebilirdin

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

Bir sırayı fermuarın sol tarafı olarak ve diğer sıralamayı fermuarın sağ tarafı olarak düşündüğünüz için "fermuar" olarak adlandırılır ve fermuar operatörü dişleri eşleştirerek iki tarafı birlikte çeker ( dizinin öğeleri) uygun şekilde.


8
Kesinlikle en iyi açıklama burada.
Maxim Gershkovich

2
Fermuar örneğini çok sevdik. Çok doğaldı. İlk izlenimim, sanki arabanızla bir caddeden geçiyormuşsunuz gibi hız veya bunun gibi bir şey olup olmadığı idi.
RBT

23

İki diziyi yineler ve öğelerini tek tek tek bir yeni dizide birleştirir. Yani, A dizisindeki bir elemanı alıp, onu B dizisindeki karşılık gelen elemanla dönüştürürsünüz ve sonuç C dizisinin bir elemanını oluşturur.

Bunu düşünmenin bir yolu Select, aynı koleksiyondaki öğeleri tek bir koleksiyondan dönüştürmek yerine, aynı anda iki koleksiyon üzerinde çalışması dışında benzer olmasıdır .

Gönderen yöntemine MSDN makalesinde :

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

Bunu emir kodu ile yapacak olsaydın, muhtemelen şöyle bir şey yapardın:

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

Veya LINQ yoksa Zip, bunu yapabilirdiniz:

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

Bu, her biri aynı uzunluk ve sıraya sahip ve her biri aynı nesne kümesinin farklı bir özelliğini açıklayan basit, dizi benzeri listelere yayılmış verileriniz olduğunda kullanışlıdır. Zipbu veri parçalarını daha tutarlı bir yapıya dönüştürmenize yardımcı olur.

Dolayısıyla, bir dizi durum adınız ve bunların başka bir kısaltmaları dizisine sahipseniz, bunları aşağıdaki Stategibi bir sınıfta harmanlayabilirsiniz :

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

Bu yanıtı da beğendim, çünküSelect
iliketocode

17

İsmin Zipsizi atmasına izin VERMEYİN . Bir dosyayı veya klasörü sıkıştırırken (sıkıştırma) olduğu gibi sıkıştırmayla ilgisi yoktur. Aslında ismini giysilerin üzerindeki bir fermuarın çalışma şeklinden alıyor: Giysilerin üzerindeki fermuarın 2 tarafı ve her iki tarafında da bir demet diş var. Tek yöne gittiğinizde fermuar her iki tarafı numaralandırır (hareket eder) ve dişleri sıkarak fermuarı kapatır. Diğer yöne gittiğinizde dişleri açar. Ya açık ya da kapalı bir fermuarla bitirirsiniz.

ZipYöntemle aynı fikirdir . İki koleksiyonumuzun olduğu bir örnek düşünün. Biri harfleri, diğeri de o harfle başlayan bir gıda maddesinin adını tutar. Açıklık amacıyla onları arıyorum leftSideOfZipperve rightSideOfZipper. İşte kod.

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

Görevimiz, meyvenin harfini a :ve adı ile ayrılmış bir koleksiyon oluşturmaktır . Bunun gibi:

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

Zipkurtarmak için. Fermuar terminolojimize ayak uydurmak için bu sonuca closedZipperve arayacağımız sol fermuarın eşyalarına leftTooth, sağ tarafa da righToothbariz nedenlerle arayacağız :

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

Yukarıda fermuarın sol tarafını ve fermuarın sağ tarafını sıralıyoruz (seyahat ediyor) ve her dişe bir işlem yapıyoruz. Yaptığımız operasyon sol dişi (yemek harfi) önce a :ve ardından sağ diş (yemek adı) ile birleştirmektir. Bunu şu kodu kullanarak yapıyoruz:

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

Sonuç şudur:

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

Son E harfine ne oldu?

Gerçek bir elbise fermuarını sayıyorsanız (çekiyorsanız) ve bir tarafı, sol tarafı veya sağ tarafı önemli değil, diğer tarafa göre daha az dişe sahipse ne olur? Peki fermuar orada duracak. ZipYöntem tamamen aynı olacaktır: Duracak o iki tarafında son öğeyi ulaştığında. Bizim durumumuzda, sağ tarafta daha az diş (yiyecek isimleri) vardır, bu yüzden "Donut" da duracaktır.


1
+1. Evet, "Zip" adı ilk başta kafa karıştırıcı olabilir. Belki de "Interleave" veya "Weave", yöntem için daha açıklayıcı isimler olabilirdi.
BACON

1
@ pastırma evet ama o zaman fermuar örneğimi kullanamazdım;) Sanırım fermuar gibi bir şey bulduğunuzda, sonrasında oldukça basit.
CodingYoshi

Zip uzatma yönteminin tam olarak ne yaptığını bilmeme rağmen, neden böyle adlandırıldığını hep merak etmişimdir. Yazılımın genel jargonunda zip her zaman başka bir anlama gelmiştir. Harika benzetme :-) Yaratıcının zihnini okumuş olmalısın.
Raghu Reddy Muttana

7

Yorumlar bölümünde yayınlayacak temsilci puanlarım yok, ancak ilgili soruyu cevaplamak için:

Bir listenin öğeleri kalmadığında zip'in devam etmesini istersem ne olur? Bu durumda, daha kısa liste öğesi varsayılan değeri almalıdır. Bu durumda çıktı A1, B2, C3, D0, E0 olacaktır. - liang 19 Kasım 2015, 3:29

Yapacağınız şey, daha kısa diziyi varsayılan değerlerle doldurmak için Array.Resize () kullanmak ve ardından bunları birlikte Zip () kullanmaktır.

Kod örneği:

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

Çıktı:

A1
B2
C3
D0
E0

Array.Resize () kullanmanın bir uyarı olduğunu lütfen unutmayın : C # içinde Redim Preserve?

Hangi dizinin daha kısa olacağı bilinmiyorsa, onu destekleyen bir işlev oluşturulabilir:

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 () ile birlikte düz .Zip () çıktısı:

A1 A1
B2 B2
C3 C3
   D0
   E0

Orijinal sorunun ana cevabına geri dönecek olursak, yapmak isteyebileceğiniz bir başka ilginç şey ("sıkıştırılacak" dizilerin uzunlukları farklı olduğunda), listenin sonuna gelecek şekilde onları birleştirmektir. üst yerine eşleşir. Bu, .Skip () kullanılarak uygun sayıda öğe "atlanarak" gerçekleştirilebilir.

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

Çıktı:

C1
D2
E3

Yeniden boyutlandırma, özellikle koleksiyonlardan herhangi biri büyükse gereksizdir. Gerçekten yapmak istediğiniz şey, koleksiyonun bitiminden sonra devam eden, isteğe bağlı olarak boş değerlerle dolduran (bir destek koleksiyonu olmadan) bir numaralandırmaya sahip olmaktır. Bunu şununla yapabilirsiniz: 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

Bir
yorumdaki

7

Buradaki cevapların çoğu gösteriyor Zip, ancak kullanımını motive edecek gerçek bir kullanım durumunu gerçekten açıklamadan Zip.

ZipBirbirini izleyen şey çiftlerini yinelemek için harika olan özellikle yaygın bir model . Bu bir enumerator yineleme yapılır X1 elemanının atlayarak, kendisi ile: x.Zip(x.Skip(1). Görsel Örnek:

 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)

Bu ardışık çiftler, değerler arasındaki ilk farkları bulmak için kullanışlıdır. Örneğin, ardışık çiftler IEnumable<MouseXPosition>üretmek için kullanılabilir IEnumerable<MouseXDelta>. Benzer şekilde, a'nın örneklenmiş booldeğerleri / / / buttongibi olaylara yorumlanabilir . Bu olaylar, daha sonra yöntemlerin temsilciliğine yönelik çağrıları yönlendirebilir. İşte bir örnek: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) );
    }
}

Baskılar:

NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked

6

Diğerlerinin de belirttiği gibi Zip, iki koleksiyonu birleştirip daha fazla Linq ifadesinde veya bir foreach döngüsünde kullanmanıza izin verir.

Bir for döngüsü ve iki dizi gerektiren işlemler artık anonim bir nesne kullanılarak bir foreach döngüsünde yapılabilir.

Az önce keşfettiğim bir örnek, bu biraz saçma, ancak paralelleştirme yararlı olsaydı yararlı olabilirdi, yan etkileri olan tek bir kuyruk geçişi olurdu:

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, bir kuyruktaki geçerli veya kuyruğu kaldırılmış öğeleri temsil eder (son öğe Zip tarafından kesilir). timeSegments.Skip (1), bir kuyruktaki sonraki veya gözetleme öğelerini temsil eder. Zip yöntemi, bu ikisini Next ve Current özelliğine sahip tek bir anonim nesnede birleştirir. Sonra Where ile filtreliyoruz ve AsParallel (). ForAll ile değişiklik yapıyoruz. Elbette son bit, normal bir foreach veya rahatsız edici zaman dilimlerini döndüren başka bir Select ifadesi olabilir.


3

Zip yöntemi, sizin tarafınızdan, yani arayan tarafından bir birleştirme işlevi sağlayıcısı kullanarak iki ilgisiz diziyi "birleştirmenize" olanak tanır. MSDN'deki örnek, Zip ile neler yapabileceğinizi göstermede oldukça iyidir. Bu örnekte, iki rastgele, ilgisiz dizi alır ve bunları rastgele bir işlev kullanarak birleştirirsiniz (bu durumda, her iki dizideki öğeleri tek bir dizede birleştirerek).

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.