Qual é o uso do Enumerable.Zipmétodo de extensão no Linq?
Qual é o uso do Enumerable.Zipmé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
Zipalternativa. B) Escrever um método para yield returncada elemento da lista mais curta, e, em seguida, continuar yield returning defaultpor 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. Zipajuda 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 Stateclasse 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 Zipmé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 leftSideOfZippere 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
Zippara o resgate. Para acompanhar a terminologia do zíper, chamaremos esse resultado closedZippere os itens do zíper esquerdo, leftTootho lado direito, e o lado direito, righToothpor 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 Zipmé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 Xcom 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 boolvalores amostrados de a buttonpodem 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