user1675891
user1675891

Reputation:

Obtaining an intersection of two IEnumerables using LINQ

I have two instances of type IEnumerable as follows.

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

Both Type1 and Type2 contain a member called common, so even though they are of different class, we can still relate them like this.

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

I'm trying to filter away those elements of type1 that have no corresponding common value in type2 and create a dictionary based on one value from each. Right now, I do that by the following double loop.

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

Now, I've tried with LINQ but all the .Where, .Select and .ForEach just gave me headache. Is there a way to neatly perform the same operation using LINQ?

Upvotes: 5

Views: 1959

Answers (5)

Eric Lippert
Eric Lippert

Reputation: 660024

When two sequences have something in common and you want to filter their product based on that commonality, the efficient query is a join. Suppose Type1 is Customer and Type2 is Order. Every customer has a CustomerID, and every order also has a CustomerID. Then you can say this.

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

Iterating that will give you a sequence of pairs consisting of every customer name that has an order, and all of their products. So if customer Suzy ordered a pancake and a pizza and customer Bob ordered a steak, you'd get these pairs.

Suzy, pancake
Suzy, pizza
Bob, steak

If instead you want those grouped so that every customer has a list of their orders, that's a group join.

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

Iterating that gives you pairs where the first item is the name and the second item is a sequence of products.

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

Upvotes: 15

Richard Schneider
Richard Schneider

Reputation: 35477

I my be missing something, but would this do:

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

Or as Servy suggested

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

Upvotes: -1

Breealzibub
Breealzibub

Reputation: 8095

Assuming that you still want to keep the intersection as a 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);

Upvotes: 0

Esteban Martinez
Esteban Martinez

Reputation: 84

Another option would be joining. I did a quick console app below, but had to make up my own data. Hope I understood your question correctly.

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

Displayed below:
first: value1
first: value3
second: value1
second: value3

Upvotes: 1

box86rowh
box86rowh

Reputation: 3415

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

This should get you a list of only those that match.

Upvotes: -1

Related Questions