104 votes

Intersection de plusieurs listes avec IEnumerable.Intersect ()

J'ai une liste de listes qui je veux trouver l'intersection pour comme ceci:

var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };

// expected intersection is List<int>() { 3 };

Est-il possible de faire cela avec IEnumerable.Intersect()?

EDIT: J'aurais dû être plus clair sur ce point: j'ai une liste de listes, je ne sais pas combien il y aura trois listes ci-dessus était juste un exemple, ce que j'ai est en fait un IEnumerable<IEnumerable<SomeClass>>

SOLUTION

Merci pour toutes les réponses grands. En fait il y a quatre options pour la résolution de ce: Liste+agrégat (@Marcel Gosselin), Liste+foreach (@JaredPar, @Gabe Moothart), HashSet+agrégat (@jesperll) et HashSet+foreach (@Tony Poney). J'ai fait quelques tests de performance sur ces solutions (variable nombre de listes, le nombre d'éléments dans chaque liste et le nombre aléatoire max taille.

Il s'avère que pour la plupart des situations, le HashSet est plus performante que la Liste (sauf avec de grandes listes et les petites de nombre aléatoire de taille, en raison de la nature de HashSet, je suppose.) Je ne pouvais pas trouver tout de réelle différence entre le foreach de la méthode et de l'ensemble de la méthode (la méthode foreach effectue légèrement mieux).

Pour moi, la méthode des agrégats est vraiment attrayant (et je vais avec ce que l'on a accepté la réponse), mais je ne dirais pas que c'est le plus lisible solution.. Merci encore à tous!

99voto

Jesper Larsen-Ledet Points 3598

Que diriez-vous:

 var intersection = listOfLists
    .Skip(1)
    .Aggregate(
        new HashSet<T>(listOfLists.First()),
        (h, e) => { h.IntersectWith(e); return h; }
    );
 

De cette façon, il est optimisé en utilisant le même HashSet tout en restant dans une seule instruction. Assurez-vous simplement que listOfLists contient toujours au moins une liste.

74voto

Jon Skeet Points 692016

En effet, vous pouvez utiliser Intersect deux fois. Cependant, je pense que cela sera plus efficace:

HashSet<int> hashSet = new HashSet<int>(list1);
hashSet.IntersectWith(list2);
hashSet.IntersectWith(list3);
List<int> intersection = hashSet.ToList();

Pas de problème avec les petits jeux bien sûr, mais si vous avez beaucoup de grandes séries, il pourrait être important.

Fondamentalement, Enumerable.Intersect besoins pour créer un ensemble à chaque appel - si vous savez que vous allez faire plus d'opérations sur les ensembles, vous pourriez aussi bien de le garder autour d'.

Comme toujours, garder un œil attentif sur les performances et la lisibilité - la méthode de chaînage de l'appel d' Intersect deux fois est très attrayant.

EDIT: Pour la mise à jour de question:

public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
    HashSet<T> hashSet = null;
    foreach (var list in lists)
    {
        if (hashSet == null)
        {
            hashSet = new HashSet<T>(list);
        }
        else
        {
            hashSet.IntersectWith(list);
        }
    }
    return hashSet == null ? new List<T>() : hashSet.ToList();
}

Ou si vous savez qu'il ne sera pas vide, et que Sauter sera relativement bon marché:

public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
    HashSet<T> hashSet = new HashSet<T>(lists.First());
    foreach (var list in lists.Skip(1))
    {
        hashSet.IntersectWith(list);
    }
    return hashSet.ToList();
}

37voto

Marcel Gosselin Points 2730

Essayez ceci, cela fonctionne, mais j'aimerais vraiment me débarrasser de .ToList () dans son ensemble.

 var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };
var intersection = listOfLists.Aggregate((previousList, nextList) => previousList.Intersect(nextList).ToList());
 

9voto

JaredPar Points 333733

Vous pouvez faire ce qui suit

 var result = list1.Intersect(list2).Intersect(list3).ToList();
 

6voto

gigi Points 322

Ceci est ma version de la solution avec une méthode d'extension que j'ai appelée IntersectMany.

 public static IEnumerable<TResult> IntersectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
    using (var enumerator = source.GetEnumerator())
    {
        if(!enumerator.MoveNext())
            return new TResult[0];

        var ret = selector(enumerator.Current);

        while (enumerator.MoveNext())
        {
            ret = ret.Intersect(selector(enumerator.Current));
        }

        return ret;
    }
}
 

Donc, l'utilisation serait quelque chose comme ça:

 var intersection = (new[] { list1, list2, list3 }).IntersectMany(l => l).ToList();
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X