À quoi sert la Enumerable.Zip
méthode d'extension dans Linq?
À quoi sert la Enumerable.Zip
méthode d'extension dans Linq?
Réponses:
L'opérateur Zip fusionne les éléments correspondants de deux séquences à l'aide d'une fonction de sélection spécifiée.
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);
Ouput
A1
B2
C3
Zip
alternative. B) Ecrire une méthode pour yield return
chaque élément de la liste plus courte, puis continuer yield return
ing default
indéfiniment par la suite. (L'option B vous oblige à savoir à l'avance quelle liste est la plus courte.)
Zip
est pour combiner deux séquences en une seule. Par exemple, si vous avez les séquences
1, 2, 3
et
10, 20, 30
et vous voulez que la séquence qui résulte de la multiplication des éléments dans la même position dans chaque séquence pour obtenir
10, 40, 90
Tu pourrais dire
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
On l'appelle «zip» parce que vous pensez à une séquence comme le côté gauche d'une fermeture à glissière et l'autre séquence comme le côté droit de la fermeture à glissière, et l'opérateur de la fermeture éclair tirera les deux côtés ensemble en les appariant des dents (le éléments de la séquence) de manière appropriée.
Il parcourt deux séquences et combine leurs éléments, un par un, en une seule nouvelle séquence. Donc, vous prenez un élément de la séquence A, le transformez avec l'élément correspondant de la séquence B, et le résultat forme un élément de la séquence C.
Une façon d'y penser est que c'est similaire Select
, sauf qu'au lieu de transformer des éléments d'une seule collection, cela fonctionne sur deux collections à la fois.
De l' article MSDN sur la méthode :
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
Si vous deviez faire cela dans un code impératif, vous feriez probablement quelque chose comme ceci:
for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
numbersAndWords.Add(numbers[i] + " " + words[i]);
}
Ou si LINQ n'en avait pas Zip
, vous pouvez le faire:
var numbersAndWords = numbers.Select(
(num, i) => num + " " + words[i]
);
Ceci est utile lorsque vous avez des données réparties dans des listes simples de type tableau, chacune avec la même longueur et le même ordre, et chacune décrivant une propriété différente du même ensemble d'objets. Zip
vous aide à assembler ces éléments de données en une structure plus cohérente.
Donc, si vous avez un tableau de noms d'états et un autre tableau de leurs abréviations, vous pouvez les rassembler dans une State
classe comme ceci:
IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
return stateNames.Zip(statePopulations,
(name, population) => new State()
{
Name = name,
Population = population
});
}
Select
NE laissez PAS le nom Zip
vous décourager. Cela n'a rien à voir avec la compression comme dans la compression d'un fichier ou d'un dossier (compression). Il tire son nom du fonctionnement d'une fermeture à glissière sur les vêtements: la fermeture à glissière des vêtements a 2 côtés et chaque côté a un tas de dents. Lorsque vous allez dans une direction, la fermeture à glissière énumère (parcourt) les deux côtés et ferme la fermeture à glissière en serrant les dents. Quand tu vas dans l'autre sens, ça ouvre les dents. Vous terminez par une fermeture éclair ouverte ou fermée.
C'est la même idée avec la Zip
méthode. Prenons un exemple où nous avons deux collections. L'un contient des lettres et l'autre le nom d'un aliment qui commence par cette lettre. Par souci de clarté, je les appelle leftSideOfZipper
et rightSideOfZipper
. Voici le code.
var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };
Notre tâche est de produire une collection dont la lettre du fruit est séparée par un :
et son nom. Comme ça:
A : Apple
B : Banana
C : Coconut
D : Donut
Zip
à la rescousse. Pour suivre notre terminologie de la fermeture à glissière, nous appellerons ce résultat closedZipper
et les éléments de la fermeture à glissière gauche que nous appellerons leftTooth
et le côté droit, nous appellerons righTooth
pour des raisons évidentes:
var closedZipper = leftSideOfZipper
.Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
Dans ce qui précède, nous énumérons (voyageant) le côté gauche de la fermeture à glissière et le côté droit de la fermeture à glissière et effectuons une opération sur chaque dent. L'opération que nous effectuons consiste à concaténer la dent gauche (lettre de l'aliment) avec a :
, puis la dent de droite (nom de l'aliment). Nous faisons cela en utilisant ce code:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
Le résultat final est le suivant:
A : Apple
B : Banana
C : Coconut
D : Donut
Qu'est-il arrivé à la dernière lettre E?
Si vous énumérez (tirez) une vraie fermeture à glissière de vêtements et qu'un côté, peu importe le côté gauche ou le côté droit, a moins de dents que l'autre côté, que se passera-t-il? Eh bien, la fermeture éclair s'arrêtera là. La Zip
méthode fera exactement la même chose: elle s'arrêtera une fois qu'elle aura atteint le dernier élément de chaque côté. Dans notre cas, le côté droit a moins de dents (noms des aliments) donc il s'arrêtera à "Donut".
Je n'ai pas les points de représentant à publier dans la section commentaires, mais pour répondre à la question connexe:
Que faire si je veux que zip continue là où une liste manque d'éléments? Dans ce cas, l'élément de liste le plus court doit prendre la valeur par défaut. Dans ce cas, la sortie doit être A1, B2, C3, D0, E0. - liang 19 novembre 15 à 3:29
Ce que vous feriez est d'utiliser Array.Resize () pour compléter la séquence plus courte avec les valeurs par défaut, puis les Zip () ensemble.
Exemple de code:
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);
Production:
A1
B2
C3
D0
E0
Veuillez noter que l'utilisation de Array.Resize () a une mise en garde : Redim Preserve en C #?
Si on ne sait pas quelle séquence sera la plus courte, une fonction peut être créée qui la résume:
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());
}
Sortie de .Zip () avec ZipDefault ():
A1 A1
B2 B2
C3 C3
D0
E0
Pour en revenir à la réponse principale de la question d'origine , une autre chose intéressante que l'on pourrait souhaiter faire (lorsque les longueurs des séquences à «zipper» sont différentes) est de les joindre de telle manière que la fin de la liste correspond au lieu du haut. Ceci peut être accompli en "sautant" le nombre approprié d'éléments en utilisant .Skip ().
foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);
Production:
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; } }
Beaucoup de réponses ici démontrent Zip
, mais sans vraiment expliquer un cas d'utilisation réel qui motiverait l'utilisation de Zip
.
Un modèle particulièrement courant qui Zip
est fantastique pour itérer sur des paires successives de choses. Cela se fait par un itérer dénombrable X
avec lui - même, en sautant 1 élément: x.Zip(x.Skip(1)
. Exemple visuel:
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)
Ces paires successives sont utiles pour trouver les premières différences entre les valeurs. Par exemple, des paires successives de IEnumable<MouseXPosition>
peuvent être utilisées pour produire IEnumerable<MouseXDelta>
. De même, les bool
valeurs échantillonnées de a button
peuvent être interprétées en événements comme NotPressed
/ Clicked
/ Held
/ Released
. Ces événements peuvent ensuite générer des appels pour déléguer des méthodes. Voici un exemple:
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) );
}
}
Impressions:
NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
Comme d'autres l'ont indiqué, Zip vous permet de combiner deux collections pour une utilisation dans d'autres instructions Linq ou une boucle foreach.
Les opérations qui nécessitaient auparavant une boucle for et deux tableaux peuvent désormais être effectuées dans une boucle foreach à l'aide d'un objet anonyme.
Un exemple que je viens de découvrir, c'est un peu idiot, mais qui pourrait être utile si la parallélisation était bénéfique serait une traversée de file d'attente sur une seule ligne avec des effets secondaires:
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 représente les éléments actuels ou retirés de la file d'attente dans une file d'attente (le dernier élément est tronqué par Zip). timeSegments.Skip (1) représente les éléments suivants ou aperçus dans une file d'attente. La méthode Zip combine ces deux éléments en un seul objet anonyme avec une propriété Next et Current. Ensuite, nous filtrons avec Where et apportons des modifications avec AsParallel (). ForAll. Bien sûr, le dernier bit pourrait simplement être une instruction foreach régulière ou une autre instruction Select qui renvoie les segments de temps incriminés.
La méthode Zip vous permet de «fusionner» deux séquences non liées, en utilisant un fournisseur de fonctions de fusion par vous, l'appelant. L'exemple sur MSDN est en fait assez bon pour démontrer ce que vous pouvez faire avec Zip. Dans cet exemple, vous prenez deux séquences arbitraires et non liées et vous les combinez à l'aide d'une fonction arbitraire (dans ce cas, il suffit de concaténer les éléments des deux séquences en une seule chaîne).
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