Trouver l'intersection de deux listes imbriquées?


468

Je sais comment obtenir une intersection de deux listes plates:

b1 = [1,2,3,4,5,9,11,15]
b2 = [4,5,6,7,8]
b3 = [val for val in b1 if val in b2]

ou

def intersect(a, b):
    return list(set(a) & set(b))

print intersect(b1, b2)

Mais quand je dois trouver une intersection pour des listes imbriquées, mes problèmes commencent:

c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]

Au final, j'aimerais recevoir:

c3 = [[13,32],[7,13,28],[1,6]]

Pouvez-vous me donner un coup de main avec ça?

en relation


Quelle serait votre intersection pour c1 intersection c2? Voulez-vous simplement savoir si c1 est dans c2? Ou voulez-vous trouver tous les éléments de c1 qui apparaissent n'importe où dans c2?
Brian R. Bondy

Lisez ceci et jouez dans l'interprète.
Pithikos

Réponses:


177

Si tu veux:

c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]
c3 = [[13, 32], [7, 13, 28], [1,6]]

Alors voici votre solution pour Python 2:

c3 = [filter(lambda x: x in c1, sublist) for sublist in c2]

En Python 3 filterretourne un itérable au lieu de list, vous devez donc encapsuler les filterappels avec list():

c3 = [list(filter(lambda x: x in c1, sublist)) for sublist in c2]

Explication:

La partie filtre prend l'élément de chaque sous-liste et vérifie s'il se trouve dans la liste source c1. La compréhension de la liste est exécutée pour chaque sous-liste en c2.


35
Vous pouvez utiliser filter(set(c1).__contains__, sublist)pour l'efficacité. btw, l'avantage de cette solution est qu'elle filter()préserve les types de chaînes et de tuples.
jfs

3
j'aime cette méthode, mais je me vide '' dans ma liste résultante
Jonathan Ong

J'ai ajouté Python 3 compat ici, car je l'utilise comme cible de dupe pour une dupe d'une question Python 3
Antti Haapala

9
Cela lit mieux l'OMI avec des compréhensions imbriquées:c3 = [[x for x in sublist if x in c1] for sublist in c2]
Eric

894

Vous n'avez pas besoin de définir l'intersection. C'est déjà une partie de première classe de l'ensemble.

>>> b1 = [1,2,3,4,5,9,11,15]
>>> b2 = [4,5,6,7,8]
>>> set(b1).intersection(b2)
set([4, 5])

3
Sera-ce plus lent que lambda en raison de la conversion en set?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

32
@ S.Lott, quelque chose ne va pas set(b1) & set(b2)? IMO son nettoyant pour utiliser l'opérateur.
gwg

4
De plus, l'utilisation setentraînera un code plus rapide de plusieurs ordres de grandeur. Voici un exemple de benchmark®: gist.github.com/andersonvom/4d7e551b4c0418de3160
andersonvom

5
Fonctionne uniquement si le résultat ne doit pas être commandé.
Borbag

7
Donc ... cette réponse ne répond en rien à la question, non? Parce que cela fonctionne maintenant avec des listes imbriquées .
Mayou36

60

Pour les personnes cherchant simplement à trouver l'intersection de deux listes, l'Asker a fourni deux méthodes:

b1 = [1,2,3,4,5,9,11,15]
b2 = [4,5,6,7,8]
b3 = [val for val in b1 if val in b2]

et

def intersect(a, b):
     return list(set(a) & set(b))

print intersect(b1, b2)

Mais il existe une méthode hybride plus efficace, car vous n'avez qu'à effectuer une conversion entre liste / ensemble, par opposition à trois:

b1 = [1,2,3,4,5]
b2 = [3,4,5,6]
s2 = set(b2)
b3 = [val for val in b1 if val in s2]

Cela fonctionnera en O (n), alors que sa méthode originale impliquant la compréhension de liste fonctionnera en O (n ^ 2)


Comme "si val dans s2" s'exécute dans O (N), la complexité de l'extrait de code proposé est également O (n ^ 2)
Romeno

8
Le cas moyen de "val in s2" est O (1) selon wiki.python.org/moin/TimeComplexity#set - ainsi sur n opérations, le temps attendu est O (n) (que le pire des cas soit O ( n) ou O (n ^ 2) dépend si ce cas moyen représente ou non un temps amorti, mais ce n'est pas très important en pratique).
D Coetzee

2
Le temps d'exécution est O (N) non pas parce qu'il est amorti mais parce que l'appartenance à l'ensemble est en moyenne O (1) (par exemple lors de l'utilisation de la table de hachage), c'est une grande différence, par exemple parce que le temps amorti est garanti.
miroB

28

L'approche fonctionnelle:

input_list = [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7]]

result = reduce(set.intersection, map(set, input_list))

et il peut être appliqué au cas plus général de 1+ listes


pour permettre la liste d'entrée vide: set(*input_list[:1]).intersection(*input_list[1:]). Version iterator ( it = iter(input_list)): reduce(set.intersection, it, set(next(it, []))). Les deux versions ne nécessitent pas de convertir toutes les listes d'entrée à définir. Ce dernier est plus efficace en mémoire.
jfs

Utilisez-le from functools import reducepour l'utiliser en Python 3. Ou mieux encore, utilisez une forboucle explicite .
TrigonaMinima

27

Version de compréhension de liste pure

>>> c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
>>> c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]
>>> c1set = frozenset(c1)

Aplatir la variante:

>>> [n for lst in c2 for n in lst if n in c1set]
[13, 32, 7, 13, 28, 1, 6]

Variante imbriquée:

>>> [[n for n in lst if n in c1set] for lst in c2]
[[13, 32], [7, 13, 28], [1, 6]]

20

L'opérateur & prend l'intersection de deux ensembles.

{1, 2, 3} & {2, 3, 4}
Out[1]: {2, 3}

Très bien, mais ce sujet concerne les listes!
Rafa0809

3
Le résultat de l'intersection de deux listes est un ensemble donc cette réponse est parfaitement valable.
shrewmouse

La liste peut contenir une valeur en double, mais pas les ensembles.
diewland

13

Une façon pythonique de prendre l'intersection de 2 listes est:

[x for x in list1 if x in list2]

2
Cette question concerne les listes imbriquées. Votre réponse ne répond pas à la question.
Thomas

8

Vous devriez aplatir en utilisant ce code (extrait de http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks ), le code n'est pas testé, mais je suis presque sûr que cela fonctionne:


def flatten(x):
    """flatten(sequence) -> list

    Returns a single, flat list which contains all elements retrieved
    from the sequence and all recursively contained sub-sequences
    (iterables).

    Examples:
    >>> [1, 2, [3,4], (5,6)]
    [1, 2, [3, 4], (5, 6)]
    >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, MyVector(8,9,10)])
    [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]"""

    result = []
    for el in x:
        #if isinstance(el, (list, tuple)):
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

Après avoir aplati la liste, vous effectuez l'intersection de la manière habituelle:


c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]

def intersect(a, b):
     return list(set(a) & set(b))

print intersect(flatten(c1), flatten(c2))

2
C'est un bon morceau d'aplatissement du code Geo, mais cela ne répond pas à la question. Le demandeur attend spécifiquement le résultat sous la forme [[13,32], [7,13,28], [1,6]].
Rob Young

8

Depuis intersectsa définition, une compréhension de base des listes suffit:

>>> c3 = [intersect(c1, i) for i in c2]
>>> c3
[[32, 13], [28, 13, 7], [1, 6]]

Amélioration grâce à la remarque de S. Lott et à la remarque associée de TM:

>>> c3 = [list(set(c1).intersection(i)) for i in c2]
>>> c3
[[32, 13], [28, 13, 7], [1, 6]]

5

Donné:

> c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]

> c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]

Je trouve que le code suivant fonctionne bien et peut-être plus concis si vous utilisez l'opération set:

> c3 = [list(set(f)&set(c1)) for f in c2] 

Il a obtenu:

> [[32, 13], [28, 13, 7], [1, 6]]

Si commande nécessaire:

> c3 = [sorted(list(set(f)&set(c1))) for f in c2] 

nous avons:

> [[13, 32], [7, 13, 28], [1, 6]]

Soit dit en passant, pour un style plus python, celui-ci est très bien aussi:

> c3 = [ [i for i in set(f) if i in c1] for f in c2]

3

Je ne sais pas si je suis en retard pour répondre à votre question. Après avoir lu votre question, j'ai trouvé une fonction intersect () qui peut fonctionner à la fois sur la liste et la liste imbriquée. J'ai utilisé la récursivité pour définir cette fonction, elle est très intuitive. J'espère que c'est ce que vous recherchez:

def intersect(a, b):
    result=[]
    for i in b:
        if isinstance(i,list):
            result.append(intersect(a,i))
        else:
            if i in a:
                 result.append(i)
    return result

Exemple:

>>> c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
>>> c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]
>>> print intersect(c1,c2)
[[13, 32], [7, 13, 28], [1, 6]]

>>> b1 = [1,2,3,4,5,9,11,15]
>>> b2 = [4,5,6,7,8]
>>> print intersect(b1,b2)
[4, 5]

2

Considérez-vous [1,2]croiser [1, [2]]? Autrement dit, est-ce seulement les chiffres qui vous intéressent, ou la structure de la liste aussi?

Si seuls les chiffres, étudiez comment "aplatir" les listes, puis utilisez la set()méthode.


Je voudrais laisser la structure des listes inchangée.
elfuego1

1

Je cherchais également un moyen de le faire, et finalement ça a fini comme ça:

def compareLists(a,b):
    removed = [x for x in a if x not in b]
    added = [x for x in b if x not in a]
    overlap = [x for x in a if x in b]
    return [removed,added,overlap]

Si vous n'utilisez pas set.intersection, ces simples lignes sont ce que je ferais également.
slaughter98

0
c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]

c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]

c3 = [list(set(c2[i]).intersection(set(c1))) for i in xrange(len(c2))]

c3
->[[32, 13], [28, 13, 7], [1, 6]]

0

Nous pouvons utiliser des méthodes définies pour cela:

c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]

   result = [] 
   for li in c2:
       res = set(li) & set(c1)
       result.append(list(res))

   print result

0

Pour définir une intersection qui prend correctement en compte la cardinalité des éléments, utilisez Counter:

from collections import Counter

>>> c1 = [1, 2, 2, 3, 4, 4, 4]
>>> c2 = [1, 2, 4, 4, 4, 4, 5]
>>> list((Counter(c1) & Counter(c2)).elements())
[1, 2, 4, 4, 4]

0
# Problem:  Given c1 and c2:
c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]
# how do you get c3 to be [[13, 32], [7, 13, 28], [1, 6]] ?

Voici une façon de définir c3qui n'implique pas d'ensembles:

c3 = []
for sublist in c2:
    c3.append([val for val in c1 if val in sublist])

Mais si vous préférez utiliser une seule ligne, vous pouvez le faire:

c3 = [[val for val in c1 if val in sublist]  for sublist in c2]

C'est une compréhension de liste à l'intérieur d'une compréhension de liste, ce qui est un peu inhabituel, mais je pense que vous ne devriez pas avoir trop de mal à la suivre.


0
c1 = [1, 6, 7, 10, 13, 28, 32, 41, 58, 63]
c2 = [[13, 17, 18, 21, 32], [7, 11, 13, 14, 28], [1, 5, 6, 8, 15, 16]]
c3 = [list(set(i) & set(c1)) for i in c2]
c3
[[32, 13], [28, 13, 7], [1, 6]]

Pour moi, c'est un moyen très élégant et rapide d'y arriver :)


0

la liste plate peut être établie reducefacilement.

Tout ce dont vous avez besoin pour utiliser l' initialiseur - troisième argument dans la reducefonction.

reduce(
   lambda result, _list: result.append(
       list(set(_list)&set(c1)) 
     ) or result, 
   c2, 
   [])

Le code ci-dessus fonctionne pour python2 et python3, mais vous devez importer le module de réduction sous from functools import reduce. Reportez-vous au lien ci-dessous pour plus de détails.


-1

Un moyen simple de trouver la différence et l'intersection entre les itérables

Utilisez cette méthode si la répétition est importante

from collections import Counter

def intersection(a, b):
    """
    Find the intersection of two iterables

    >>> intersection((1,2,3), (2,3,4))
    (2, 3)

    >>> intersection((1,2,3,3), (2,3,3,4))
    (2, 3, 3)

    >>> intersection((1,2,3,3), (2,3,4,4))
    (2, 3)

    >>> intersection((1,2,3,3), (2,3,4,4))
    (2, 3)
    """
    return tuple(n for n, count in (Counter(a) & Counter(b)).items() for _ in range(count))

def difference(a, b):
    """
    Find the symmetric difference of two iterables

    >>> difference((1,2,3), (2,3,4))
    (1, 4)

    >>> difference((1,2,3,3), (2,3,4))
    (1, 3, 4)

    >>> difference((1,2,3,3), (2,3,4,4))
    (1, 3, 4, 4)
    """
    diff = lambda x, y: tuple(n for n, count in (Counter(x) - Counter(y)).items() for _ in range(count))
    return diff(a, b) + diff(b, a)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.