2013-02-09 17 views
5

Mam dwa wystąpienia typu IEnumerable w następujący sposób.Uzyskiwanie przecięcia dwóch obiektów IEnumerables przy użyciu LINQ

IEnumerable<Type1> type1 = ...; 
IEnumerable<Type2> type2 = ...; 

Zarówno Type1 i Type2 zawierać członkiem nazywa common, więc mimo, że są z innej klasy, możemy nadal odnosić się je w ten sposób.

type1[0].common == type2[4].common 

Próbuję filtrować stąd te elementy type1, które nie mają odpowiedniej wartości w type2common i stworzenia słownika w oparciu o wartość z każdym. Teraz robię to przez następującą podwójną pętlę.

Dictionary<String, String> intersection = ...; 
foreach (Type1 t1 in type1) 
    foreach(Type2 t2 in type2) 
    if (t1.common == t2.common) 
     intersection.Add(t1.Id, t2.Value); 

Teraz Próbowałem z LINQ, ale wszystkie .Where, .Select i .ForEach po prostu dał mi ból głowy. Czy istnieje sposób na staranne wykonanie tej samej operacji za pomocą LINQ?

+0

LINQ po prostu wyliczy. Czy masz problemy z wydajnością? – Paparazzi

+0

Nie. Ale inni programiści śmieją się ze mnie. :(Mówią, że jestem zmęczony, aby znaleźć kod i to nie jest możliwe. Chociaż mogę przyznać, pierwszy, nie zgadzam się na to drugie. :) –

+1

Co się dzieje, gdy masz t1, który ma * dwa * t2s Ten mecz? –

Odpowiedz

14

Gdy dwie sekwencje mają coś wspólnego i chcesz filtrować swój produkt oparty na tej wspólności, efektywne zapytania jest dołączyć. Załóżmy, że Type1 jest Klient i Type2 jest Zamówienie. Każdy klient ma CustomerID, a każde zamówienie ma również CustomerID. Możesz to powiedzieć.

var query = from customer in customers 
      join order in orders 
       on customer.CustomerId equals order.CustomerId 
      select new { customer.Name, order.Product }; 

Iterowanie, które da ci sekwencję par składającą się z każdej nazwy klienta, która ma zamówienie i wszystkich swoich produktów. Więc jeśli klient Suzy zamówił naleśnik i pizzę a klient zamówił stek, dostaniesz te pary.

Suzy, pancake 
Suzy, pizza 
Bob, steak 

Jeśli zamiast tego chcesz te pogrupowane, aby każdy klient miał listę swoich zamówień, jest to połączenie grupowe.

var query = from customer in customers 
      join order in orders 
       on customer.CustomerId equals order.CustomerId 
       into products 
      select new { customer.Name, products }; 

Iterowanie, które daje pary, w których pierwszym elementem jest nazwa, a drugim elementem jest ciąg produktów.

Suzy, { pancake, pizza } 
Bob, { steak } 
+0

Czyste złoto, hihi! W tym czasie pierwsze podejście jest właśnie tym, czego potrzebowałem (nie wiem, jak sam nie mogłem go skonfigurować). Ale jeden jest dla mnie nowy. Nie wiedziałem o zgrupowaniu, ale ** przyda się wkrótce. –

+0

Sądzę, że opublikowanie równoważnych wywołań metod rozszerzeń dodatkowo wyrażenie zapytań ułatwiłoby zrozumienie, co się dzieje i jakie są cechy wydajności. – CodesInChaos

-1
type1.where(i=>type2.where(j=>j.common == i.common).Count > 0); 

To powinno Ci dać listę tylko tych, które pasują.

+0

To jest trochę mniej wydajne niż "Dołącz". Wykonujesz wiele iteracji 'type2' tutaj poprzez liniowe przeszukiwanie, w przeciwieństwie do wykonywania opartych na zestawie operacji na zestawach. Nie jesteś także parinigiem elementu 't1' z wiązaniami w' type2'. – Servy

1

Inna opcja będzie dołączenie. Zrobiłem poniżej szybką aplikację konsolową, ale musiałem wymyślić własne dane. Mam nadzieję, że dobrze zrozumiałem twoje pytanie.

public class Type1 
{ 
    public string ID { get; set; } 
    public Guid common { get; set; } 
} 
public class Type2 
{ 
    public string Value { get; set; } 
    public Guid common { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Guid CommonGuid = Guid.NewGuid(); 

     IEnumerable<Type1> EnumType1 = new List<Type1>() 
     { 
      new Type1() { 
       ID = "first", 
       common = CommonGuid 
      }, 
      new Type1() { 
       ID = "second", 
       common = CommonGuid 
      }, 
      new Type1() { 
       ID = "third", 
       common = Guid.NewGuid() 
      } 
     } as IEnumerable<Type1>; 

     IEnumerable<Type2> EnumType2 = new List<Type2>() 
     { 
      new Type2() { 
       Value = "value1", 
       common = CommonGuid 
      }, 
      new Type2() { 
       Value = "value2", 
       common = Guid.NewGuid() 
      }, 
      new Type2() { 
       Value = "value3", 
       common = CommonGuid 
      } 
     } as IEnumerable<Type2>; 

     //--The part that matters 
     EnumType1      //--First IEnumerable 
      .Join(      //--Command 
       EnumType2,    //--Second IEnumerable 
       outer => outer.common, //--Key to join by from EnumType1 
       inner => inner.common, //--Key to join by from EnumType2 
       (inner, outer) => new { ID = inner.ID, Value = outer.Value }) //--What to do with matching "rows" 
      .ToList() //--Not necessary, just used so that I can use the foreach below 
      .ForEach(item => 
       { 
        Console.WriteLine("{0}: {1}", item.ID, item.Value); 
       }); 

     Console.ReadKey(); 
    } 
} 

wyświetlane poniżej:
pierwsze: VALUE1
pierwszy: wartość3
sekund: wartosc1
sekund: wartość3

+0

Yupp. Dobry przykład - bardzo jasny i pouczający. Udało mi się skrócić operację do zaledwie czterech linii LINQ, skracając tę ​​operację. –

0

Zakładając, że nadal chce utrzymać skrzyżowanie jako Dictionary<string, string>:

IEnumerable<Type1> list1; 
IEnumerable<Type2> list2; 

Dictionary<string, string> intersection = 
    (from item1 in list1 
    from item2 in list2 
    where item1.common = item2.common 
    select new { Key = item1.Id, Value = item2.Value }) 
     .ToDictionary(x => x.Key, x => x.Value); 
+0

To jest trochę mniej wydajne niż "Dołącz". Tworzysz wiele par, a następnie je filtrujesz, w przeciwieństwie do łączenia, które od samego początku tworzy wszystkie właściwe pary. – Servy

-1

Brakuje mi czegoś, ale woul d to zrobić:

type1 
.where(t1 => type2.Any(t2 => t1.common == t2.common) 
.ToDictionary(t1 => t1.Id) 

Albo jak Servy zasugerował

type1 
    .Join(type2, a => a.common, b => b.common, (a1,b1) => a1) 
    .ToDictionary(t1 => t1.Id) 
+0

To jest trochę mniej wydajne niż "Dołącz". Wykonujesz wiele iteracji 'type2' tutaj poprzez liniowe przeszukiwanie, w przeciwieństwie do wykonywania opartych na zestawie operacji na zestawach. Nie jesteś także parinigiem elementu 't1' z wiązaniami w' type2'. – Servy

+0

@Servy, masz rację. Wydajność nigdy nie była problemem. Próbowałem przekazać koncepcje LINQ komuś, kto jest bardziej zaznajomiony z pętlami. Dlaczego nie zamieścisz swojej odpowiedzi! –

+0

Dlaczego miałbym opublikować odpowiedź, w której są już dwie odpowiedzi, które rozwiązują ten problem * właściwie *, szczególnie gdy jest się z Eric Lippert. Przekazywanie koncepcji LINQ, w mojej głowie, oznacza nauczenie odpowiednich metod dla danego zadania, którym to nie jest. Z reguły za każdym razem, gdy widzisz wzorzec, z którego korzystasz 'Gdzie' z zagnieżdżonym' Any', coś jest nie tak. 'type2' powinno być' HashSet' używając 'Contains' jeśli to w ogóle możliwe. W każdym razie to rozwiązanie * nawet nie działa *. Wynik musi być * parą * elementów, a nie tylko jedną z nich. – Servy

Powiązane problemy