Qual é o uso do Enumerable.Zip
método de extensão no Linq?
Qual é o uso do Enumerable.Zip
método de extensão no Linq?
Respostas:
O operador Zip mescla os elementos correspondentes de duas seqüências usando uma função seletora especificada.
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
alternativa. B) Escrever um método para yield return
cada elemento da lista mais curta, e, em seguida, continuar yield return
ing default
por tempo indeterminado. (Opção B requer que você sabe de antemão que a lista é mais curto.)
Zip
é para combinar duas seqüências em uma. Por exemplo, se você tiver as sequências
1, 2, 3
e
10, 20, 30
e você deseja que a sequência resultante da multiplicação de elementos na mesma posição em cada sequência obtenha
10, 40, 90
você poderia dizer
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
É chamado de "zip" porque você pensa em uma sequência como o lado esquerdo de um zíper, e a outra sequência como o lado direito do zíper, e o operador de zip puxará os dois lados juntos emparelhando os dentes (o elementos da sequência) adequadamente.
Ele itera através de duas seqüências e combina seus elementos, um por um, em uma única nova seqüência. Então você pega um elemento da sequência A, transforma-o com o elemento correspondente da sequência B e o resultado forma um elemento da sequência C.
Uma maneira de pensar é que é semelhante a Select
, exceto que, em vez de transformar itens de uma única coleção, funciona em duas coleções ao mesmo tempo.
No artigo do MSDN sobre o método :
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
Se você fizesse isso em código imperativo, provavelmente faria algo assim:
for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
numbersAndWords.Add(numbers[i] + " " + words[i]);
}
Ou se o LINQ não continha Zip
, você poderia fazer o seguinte:
var numbersAndWords = numbers.Select(
(num, i) => num + " " + words[i]
);
Isso é útil quando você tem dados espalhados em listas simples, semelhantes a matrizes, cada uma com o mesmo comprimento e ordem, e cada uma descrevendo uma propriedade diferente do mesmo conjunto de objetos. Zip
ajuda a reunir esses dados em uma estrutura mais coerente.
Portanto, se você tiver uma matriz de nomes de estados e outra matriz de abreviações, poderá agrupá-las em uma State
classe como esta:
IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
return stateNames.Zip(statePopulations,
(name, population) => new State()
{
Name = name,
Population = population
});
}
Select
NÃO deixe o nome Zip
jogá-lo fora. Não tem nada a ver com compactar, como compactar um arquivo ou uma pasta (compactar). Na verdade, ele recebe o nome de como funciona o zíper nas roupas: o zíper nas roupas tem dois lados e cada lado tem um monte de dentes. Quando você segue uma direção, o zíper enumera (viaja) dos dois lados e fecha o zíper apertando os dentes. Quando você vai na outra direção, ele abre os dentes. Você termina com um zíper aberto ou fechado.
É a mesma idéia com o Zip
método. Considere um exemplo em que temos duas coleções. Um contém letras e o outro contém o nome de um item alimentar que começa com essa letra. Para fins de clareza, eu os estou chamando leftSideOfZipper
e rightSideOfZipper
. Aqui está o código.
var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };
Nossa tarefa é produzir uma coleção que tenha a letra da fruta separada por a :
e seu nome. Como isso:
A : Apple
B : Banana
C : Coconut
D : Donut
Zip
para o resgate. Para acompanhar a terminologia do zíper, chamaremos esse resultado closedZipper
e os itens do zíper esquerdo, leftTooth
o lado direito, e o lado direito, righTooth
por razões óbvias:
var closedZipper = leftSideOfZipper
.Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
No exposto acima, estamos enumerando (deslocando) o lado esquerdo do zíper e o lado direito do zíper e realizando uma operação em cada dente. A operação que estamos realizando está concatenando o dente esquerdo (letra do alimento) com um :
e depois o dente direito (nome do alimento). Fazemos isso usando este código:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
O resultado final é este:
A : Apple
B : Banana
C : Coconut
D : Donut
O que aconteceu com a última letra E?
Se você está enumerando (puxando) um zíper de roupa real e um lado, não importa o lado esquerdo ou o lado direito, tem menos dentes do que o outro lado, o que acontecerá? Bem, o zíper vai parar por aí. O Zip
método fará exatamente o mesmo: será interrompido quando atingir o último item de cada lado. No nosso caso, o lado direito tem menos dentes (nomes de alimentos) e, portanto, para em "Donut".
Não tenho os pontos de representante para postar na seção de comentários, mas para responder à pergunta relacionada:
E se eu quiser que o zip continue onde uma lista fica sem elementos? Nesse caso, o elemento da lista mais curto deve assumir o valor padrão. A saída neste caso deve ser A1, B2, C3, D0, E0. # Liang Nov 19 '15 às 3:29
O que você faria é usar Array.Resize () para preencher a sequência mais curta com os valores padrão e depois Zip () juntos.
Exemplo de código:
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);
Resultado:
A1
B2
C3
D0
E0
Observe que o uso de Array.Resize () tem uma ressalva : Redim Preserve in C #?
Se não se sabe qual sequência será a mais curta, uma função pode ser criada que a exclua:
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());
}
Saída do .Zip () comum ao lado de ZipDefault ():
A1 A1
B2 B2
C3 C3
D0
E0
Voltando à resposta principal da pergunta original , outra coisa interessante que alguém pode querer fazer (quando os comprimentos das seqüências a serem "compactadas" são diferentes) é juntá-las de tal maneira que o final da lista corresponde ao invés do topo. Isso pode ser feito "pulando" o número apropriado de itens usando .Skip ().
foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);
Resultado:
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; } }
Muitas das respostas aqui demonstram Zip
, mas sem realmente explicar um caso de uso da vida real que motivaria o uso Zip
.
Um padrão particularmente comum que Zip
é fantástico para iterar sobre pares sucessivos de coisas. Isso é feito por iteração um enumeráveis X
com ele mesmo, pulando 1 elemento: x.Zip(x.Skip(1)
. Exemplo visual:
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)
Esses pares sucessivos são úteis para encontrar as primeiras diferenças entre os valores. Por exemplo, pares sucessivos de IEnumable<MouseXPosition>
podem ser usados para produzir IEnumerable<MouseXDelta>
. Da mesma forma, os bool
valores amostrados de a button
podem ser interpretados em eventos como NotPressed
/ Clicked
/ Held
/ Released
. Esses eventos podem direcionar chamadas para delegar métodos. Aqui está um exemplo:
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) );
}
}
Impressões:
NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
Como já foi dito, o Zip permite combinar duas coleções para uso em outras instruções do Linq ou em um loop foreach.
As operações que costumavam exigir um loop for e duas matrizes agora podem ser feitas em um loop foreach usando um objeto anônimo.
Um exemplo que acabei de descobrir, que é meio bobo, mas poderia ser útil se a paralelização fosse benéfica, seria uma travessia de fila única com efeitos colaterais:
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 representa os itens atuais ou desenfileirados em uma fila (o último elemento é truncado por Zip). timeSegments.Skip (1) representa os itens seguintes ou espiar em uma fila. O método Zip combina esses dois em um único objeto anônimo com a propriedade Next e Current. Em seguida, filtramos com Where e fazemos alterações com AsParallel (). ForAll. É claro que o último bit poderia ser apenas uma foreach regular ou outra instrução Select que retorne os segmentos de tempo ofensivos.
O método Zip permite "mesclar" duas seqüências não relacionadas, usando um provedor de função de mesclagem por você, o chamador. O exemplo no MSDN é realmente muito bom em demonstrar o que você pode fazer com o Zip. Neste exemplo, você pega duas seqüências arbitrárias não relacionadas e as combina usando uma função arbitrária (nesse caso, apenas concatenando itens de ambas as seqüências em uma única sequência).
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