Posted by: Cirilo Meggiolaro | 04/22/2009

Tip of the day #190 – Intersecting collections

Intersecting results of two IEnumerable classes is pretty straightforward. An extension method called Intersect is available for objects that implement the IEnumerable interface.

Overloads

Two overloads are available:

  • IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second);
  • IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);

How to…

To start you need two collections you want to match. For this example we are going to create two generic lists for a custom type called City as defined below:

public class City
{
    public string Name { get; set; }
    public int ID { get; set; }
}

It is time to create the lists and populate them:

City city1 = new City() { ID = 1, Name = “Sao Paulo” };
City city2 = new City() { ID = 2, Name = “Toronto” };
City city3 = new City() { ID = 3, Name = “New York” };
City city4 = new City() { ID = 4, Name = “Tokyo” };
City city5 = new City() { ID = 5, Name = “Paris” };
City city6 = new City() { ID = 6, Name = “Lisbon” };

List<City> cities = new List<City>();

cities.Add(city1);
cities.Add(city2);
cities.Add(city3);
cities.Add(city4);
cities.Add(city5);
cities.Add(city6);

List<City> cities2 = new List<City>();

cities2.Add(city1);
cities2.Add(city3);
cities2.Add(city6);

To check the items both lists have in common we are going to use the Intersect extension method that will match all items that are similar and retrieve them:

IEnumerable<City> commonCities = cities.Intersect(cities2); // or
List<City> commonCities = cities.Intersect(cities2).ToList();

Let’s check the results:

Console.WriteLine(commonCities.Count()); // Output: 3

foreach (City city in commonCities)
{
    Console.WriteLine(city.Name);
}

Output:
Sao Paulo
New York
Lisbon

That is a basic scenario but sometimes you will not have the same instance of the city object in the lists resulting in a match that does not retrieve any item. The workaround for that is to use the second Intersect method overload that accepts a generic IEqualityComparer object as parameter. If you haven’t worked with IEqualityComparer yet, it’s a good time to check the Tip #42. The code below defines our class that will match the cities based on its ID:

public class CityComparer : IEqualityComparer<City>
{
    public bool Equals(City city1, City city2)
    {
        return city1.ID == city2.ID;
    }

    public int GetHashCode(City city)
    {
        return city.ID;
    }
}

Let’s recreate our lists assuming we have different instances for each city in both collections:

List<City> cities = new List<City>();

cities.Add(new City() { ID = 1, Name = “Sao Paulo” });
cities.Add(new City() { ID = 2, Name = “Toronto” });
cities.Add(new City() { ID = 3, Name = “New York” });
cities.Add(new City() { ID = 4, Name = “Tokyo” });
cities.Add(new City() { ID = 5, Name = “Paris” });
cities.Add(new City() { ID = 6, Name = “Lisbon” });

List<City> cities2 = new List<City>();

cities2.Add(new City() { ID = 1, Name = “Sao Paulo” });
cities2.Add(new City() { ID = 2, Name = “Toronto” });
cities2.Add(new City() { ID = 3, Name = “New York” });

To match the collections properly we need an instance of our comparer class:

IEnumerable<City> commonCities = cities.Intersect(cities2, new CityComparer()); // or
List<City> commonCities = cities.Intersect(cities, new CityComparer()).ToList();

Let’s check the results:

Console.WriteLine(commonCities.Count()); // Output: 3

foreach (City city in commonCities)
{
    Console.WriteLine(city.Name);
}

Output:
Sao Paulo
Toronto
New York

Work with IEnumerable collections is really easy since several extension methods are available for different tasks.

Enjoy it.


Leave a comment

Categories