11

I'm trying to find an intersect with LINQ.

Sample:

List<int> int1 = new List<int>() { 1,2 };
List<int> int2 = new List<int>();
List<int> int3 = new List<int>() { 1 };
List<int> int4 = new List<int>() { 1, 2 };
List<int> int5 = new List<int>() { 1 };

Want to return: 1 as it exists in all lists.. If I run:

var intResult= int1
            .Intersect(int2)
            .Intersect(int3)
            .Intersect(int4)
            .Intersect(int5).ToList();

It returns nothing as 1 obviously isn't in the int2 list. How do I get this to work regardless if one list is empty or not ?

Use the above example or:

List<int> int1 = new List<int>() { 1,2 };
List<int> int2 = new List<int>();
List<int> int3 = new List<int>();
List<int> int4 = new List<int>();
List<int> int5 = new List<int>();

How do I return 1 & 2 in this case.. I don't know ahead of time if the lists are populated...

Kobi
  • 135,331
  • 41
  • 252
  • 292
gavin stevens
  • 191
  • 2
  • 2
  • 4
  • 4
    Consider the intersection of `{1}, {2}, {3}`. After intersecting the first two, you're then trying to find the intersection of `{}, {3}`. And you're asking for us to come up with something that will give `{}` in this case, but `{3}` if we performed the same operation on `{3}, {}`. If you don't want empty lists to be considered, then remove them from consideration before trying to work out the intersection. – Anon. Jul 07 '10 at 04:02
  • @Anon, interesting observation. – Anthony Pegram Jul 07 '10 at 04:09
  • How would I manage that??? if (int2.Count >0) var set = int1.Intersect(int2); if (int3.Count >0) var set2 = set.Intersect(set); if (int2.Count >0) var set3 = int2.Intersect(int2); ..... Seems kinda hokey... – gavin stevens Jul 07 '10 at 04:10
  • the point that he makes is that the intersection of {1} and {2} is an empty list, so the intersection of *that* with {3} is {3}. That's a pitfall of allowing empty sets to be ignored by an Intersect method, as empty *results* get ignored as well as you chain the methods. – Anthony Pegram Jul 07 '10 at 04:14
  • That's the question then... Isn't there some way to ignore empty sets without checking to see if they are empty? – gavin stevens Jul 07 '10 at 04:28

2 Answers2

16

If you need it in a single step, the simplest solution is to filter out empty lists:

public static IEnumerable<T> IntersectNonEmpty<T>(this IEnumerable<IEnumerable<T>> lists)
{
    var nonEmptyLists = lists.Where(l => l.Any());
    return nonEmptyLists.Aggregate((l1, l2) => l1.Intersect(l2));
}

You can then use it on a collection of lists or other IEnumerables:

IEnumerable<int>[] lists = new[] { l1, l2, l3, l4, l5 };
var intersect = lists.IntersectNonEmpty();

You may prefer a regular static method:

public static IEnumerable<T> IntersectNonEmpty<T>(params IEnumerable<T>[] lists)
{
    return lists.IntersectNonEmpty();
}

var intersect = ListsExtensionMethods.IntersectNonEmpty(l1, l2, l3, l4, l5);
Kobi
  • 135,331
  • 41
  • 252
  • 292
  • 4
    Just a minor warning: thanks to the Aggregate(), this extension method will throw an InvalidOperationException with "sequence contains no elements" if all of the lists are empty. – Gavin Jan 26 '12 at 13:57
2

You could write an extension method to define that behaviour. Something like

static class MyExtensions
{
    public static IEnumerable<T> IntersectAllIfEmpty<T>(this IEnumerable<T> list, IEnumerable<T> other)
    {
        if (other.Any())
            return list.Intersect(other);
        else
            return list;
    }
}

So the code below would print 1.

List<int> list1 = new List<int>() { 1, 2 };
List<int> list2 = new List<int>();
List<int> list3 = new List<int>() { 1 };

foreach (int i in list1.IntersectAllIfEmpty(list2).IntersectAllIfEmpty(list3))
    Console.WriteLine(i);

Update:

Anon brings up a good point in the comments to the question. The above function will result in an empty set if list itself is empty, which should be desirable. This means if the first list in the method chain or the result set of any intersection is empty, the final result will be empty.

To allow for an empty first list but not for empty result sets, you could take a different approach. This is a method which is not an extension method, but rather takes a params array of IEnumerables and first filters out the empty sets and then attempts to intersect the rest.

public static IEnumerable<T> IntersectAllIfEmpty<T>(params IEnumerable<T>[] lists)
{
    IEnumerable<T> results = null;

    lists = lists.Where(l => l.Any()).ToArray();

    if (lists.Length > 0)
    {
        results = lists[0];

        for (int i = 1; i < lists.Length; i++)
            results = results.Intersect(lists[i]);
    }
    else
    {
        results = new T[0];
    }

    return results;
}

You would use it like this

List<int> list0 = new List<int>();
List<int> list1 = new List<int>() { 1, 2 };
List<int> list2 = new List<int>() { 1 };
List<int> list3 = new List<int>() { 1,2,3 };

foreach (int i in IntersectAllIfEmpty(list0, list1, list2, list3))
{
    Console.WriteLine(i);
}
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • where would I declare the extension method? Gives me: Extension method can only be decalred in non-generic, non-nested static class just trying to do this in a method.. – gavin stevens Jul 07 '10 at 04:06
  • You would put that in a static class. I've added the a class definition to my answer. – Anthony Pegram Jul 07 '10 at 04:08