3

I have a list of lists:

List<Tuple<string, List<SomeObject>>

I want to select all SomeObjects that exist in all rows of the list above.

Some will just exist in one or two lists, but I want all objects that exist in every single list, with other objects discarded.

I can't figure out an elegant solution to this without a bunch of c# code. Is there a nice way?

Soner Gönül
  • 91,172
  • 101
  • 184
  • 324
NibblyPig
  • 46,891
  • 62
  • 180
  • 311

4 Answers4

7
list.Select (x => x.Item2 as IEnumerable<SomeObject>)
    .Aggregate((x,y)=> x.Intersect(y))
    .ToList();

Or, as Jeppe Stig Nielsen suggested (and I think it's much more elegant):

list.Select(x => x.Item2.AsEnumerable())
    .Aggregate(Enumerable.Intersect)
    .ToList();
Juan Lopes
  • 9,563
  • 2
  • 23
  • 41
  • No need for `as IEnumerable` – dcastro Jan 13 '15 at 12:42
  • If you're using mono, you need it. – Juan Lopes Jan 13 '15 at 12:43
  • Really?!! Why's that though? If `Intersect` is defined for `IEnumerable` it should also be defined for `List`... – dcastro Jan 13 '15 at 12:44
  • You need it also without mono in .NET 4. – Tim Schmelter Jan 13 '15 at 12:51
  • @TimSchmelter Why's that? – dcastro Jan 13 '15 at 12:52
  • 1
    I think because there isn't really a List.Intersect. It is defined in Enumerable class, and it returns an IEnumerable, not a List. And this overload of Aggregate needs that both arguments and the return to be of same type. Somehow the return being an IEnumerable isn't enough for the type inference to know that both arguments must be IEnumerable also. – Juan Lopes Jan 13 '15 at 13:01
  • 1
    Your solution can also be written `list.Select(x => x.Item2.AsEnumerable()).Aggregate(Enumerable.Intersect).ToList();`, of course. – Jeppe Stig Nielsen Jan 13 '15 at 13:13
  • 2
    The relevant overload of `Aggregate<>` has just one type parameter `TSource`. You need `TSource` to be `IEnumerable`, not `List`. So the inclusion of "as enumerable" helps with that. An alternative is to give the type argument explicitly, of course. Just to emphasize that it is possible to use `TSource == List`, this also works: `list.Select(x => x.Item2).Aggregate((x, y) => x.Intersect(y).ToList());` (don't use, creates new `List<>` instance for each iteration). – Jeppe Stig Nielsen Jan 13 '15 at 13:16
1

As I understand correctly, You need intersection of many lists:

var results = source.First().Item2
foreach (var element in source.Skip(1) )
{
    results = results.Intersect(element.Item2)
}
py3r3str
  • 1,819
  • 17
  • 23
1

Strongly inspired by Juan Lopes's beautiful answer, you can define this extension:

static IEnumerable<TSource> IntersectMany<TSource>(this IEnumerable<IEnumerable<TSource>> sources)
{
  return sources.Aggregate(Enumerable.Intersect);
}

then this works:

var result = list.Select(x => x.Item2).IntersectMany();

It works because IEnumerable<out T> is covariant in T (C# 4, .NET 4.0).

Jeppe Stig Nielsen
  • 54,796
  • 9
  • 96
  • 154
-1

Presuming that the class overrides Equals + GetHashCode or you have a custom IEqualityComparer<SomeObject> you can use following query which uses Enumerable.All:

var result = list
    .SelectMany(t => t.Item2)   // select all objects
    .Distinct()                 // just an optimization since duplicates are guaranteed
    .Where(obj => list.All(t => t.Item2.Contains(obj))); 

Here's my sample data:

var list = new List<Tuple<string, List<SomeObject>>>();
list.Add(Tuple.Create("a", new List<SomeObject> { new SomeObject { ID = 1 }, new SomeObject { ID = 2 }, new SomeObject { ID = 4 } }));
list.Add(Tuple.Create("b", new List<SomeObject> { new SomeObject { ID = 1 }, new SomeObject { ID = 2 }, new SomeObject { ID = 3 } }));
list.Add(Tuple.Create("c", new List<SomeObject> { new SomeObject { ID = 1 }, new SomeObject { ID = 2 }, new SomeObject { ID = 3 } }));
list.Add(Tuple.Create("d", new List<SomeObject> { new SomeObject { ID = 1 }, new SomeObject { ID = 2 }, new SomeObject { ID = 3 } }));

Only the SomeObjects with ID = 1 or ID = 2 are in all lists and that's the result of the query.

Tim Schmelter
  • 411,418
  • 61
  • 614
  • 859
  • Any reason for the downvote or just boredom? It's not the most efficient or shortest way but it's straight-forward translation of this requirement, isn't it? – Tim Schmelter Jan 13 '15 at 14:09