Trova intersezione di due elenchi nidificati?


468

So come ottenere un'intersezione di due elenchi piatti:

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]

o

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

print intersect(b1, b2)

Ma quando devo trovare l'intersezione per gli elenchi nidificati, i miei problemi iniziano:

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]]

Alla fine vorrei ricevere:

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

Ragazzi, potete darmi una mano con questo?

Relazionato


Quale sarebbe il tuo incrocio per c1 intersect c2? Vuoi semplicemente scoprire se c1 è in c2? O vuoi trovare tutti gli elementi in c1 che appaiono ovunque in c2?
Brian R. Bondy,

Leggi questo e suona nell'interprete.
Pithikos,

Risposte:


177

Se vuoi:

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]]

Quindi ecco la tua soluzione per Python 2:

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

In Python 3 filterrestituisce un iterabile anziché list, quindi è necessario racchiudere le filterchiamate con list():

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

Spiegazione:

La parte del filtro accetta ogni elemento della lista secondaria e controlla se è presente nella lista sorgente c1. La comprensione dell'elenco viene eseguita per ogni elenco secondario in c2.


35
Puoi usare filter(set(c1).__contains__, sublist)per efficienza. a proposito, il vantaggio di questa soluzione è che filter()preserva i tipi di stringhe e tuple.
jfs

3
mi piace questo metodo, ma mi sto svuotando '' nella mia lista risultante
Jonathan Ong

Ho aggiunto Python 3 compat qui, dal momento che sto usando questo come target duplicato per un duplicato di una domanda Python 3
Antti Haapala,

9
Questo legge meglio l'IMO con comprensioni nidificate:c3 = [[x for x in sublist if x in c1] for sublist in c2]
Eric

894

Non è necessario definire l'intersezione. È già una parte di prima classe del set.

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

3
Sarà più lento di lambda a causa della conversione da impostare?
Ciro Santilli 26 冠状 病 六四 事件 法轮功

32
@ S.Lott, c'è qualcosa che non va set(b1) & set(b2)? IMO è più pulito per usare l'operatore.
gwg

4
Inoltre, l'utilizzo setporterà a un codice più veloce degli ordini di grandezza. Ecco un esempio benchmark®: gist.github.com/andersonvom/4d7e551b4c0418de3160
andersonvom

5
Funziona solo se non è necessario ordinare il risultato.
Borbag,

7
Quindi ... questa risposta non risponde in alcun modo alla domanda, giusto? Perché ora funziona con elenchi nidificati .
Mayou36,

60

Per le persone che cercano solo di trovare l'intersezione di due elenchi, Asker ha fornito due metodi:

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]

e

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

print intersect(b1, b2)

Ma esiste un metodo ibrido che è più efficiente, perché devi fare solo una conversione tra list / set, anziché tre:

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

Questo verrà eseguito in O (n), mentre il suo metodo originale che comprende la comprensione dell'elenco verrà eseguito in O (n ^ 2)


Poiché "se val in s2" viene eseguito in O (N), la complessità dello snippet di codice proposta è anche O (n ^ 2)
Romeno

8
Il caso medio di "val in s2" è O (1) secondo wiki.python.org/moin/TimeComplexity#set - quindi in n operazioni il tempo previsto è O (n) (se il tempo peggiore è O ( n) o O (n ^ 2) dipende dal fatto che questo caso medio rappresenti o meno un tempo ammortizzato, ma ciò non è molto importante nella pratica).
D Coetzee,

2
Il runtime è O (N) non perché è ammortizzato ma poiché l'insieme dei membri è nella media O (1) (ad esempio quando si utilizza la tabella hash), è una grande differenza, ad esempio perché il tempo ammortizzato è garantito.
miroB,

28

L'approccio funzionale:

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))

e può essere applicato al caso più generale di elenchi 1+


per permettere lista di input vuoto: set(*input_list[:1]).intersection(*input_list[1:]). Versione Iterator ( it = iter(input_list)): reduce(set.intersection, it, set(next(it, []))). Entrambe le versioni non richiedono di convertire tutti gli elenchi di input da impostare. Quest'ultimo è più efficiente in termini di memoria.
jfs,

Utilizzalo from functools import reduceper usarlo in Python 3. O meglio ancora, usa un forciclo esplicito .
Trigona Minima,

27

Versione di pura comprensione dell'elenco

>>> 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)

Variante appiattita:

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

Variante nidificata:

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

20

L'operatore & prende l'intersezione di due serie.

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

Bene, ma questo argomento è per le liste!
Rafa0809,

3
Il risultato dell'intersezione di due elenchi è un insieme, quindi questa risposta è perfettamente valida.
shrewmouse,

L'elenco può contenere un valore duplicato ma i set no.
Diewland,

13

Un modo pitonico di prendere l'intersezione di 2 liste è:

[x for x in list1 if x in list2]

2
Questa domanda riguarda gli elenchi nidificati. La tua risposta non risponde alla domanda.
Thomas,

8

Dovresti appiattire usando questo codice (preso da http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks ), il codice non è testato, ma sono abbastanza sicuro che funzioni:


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

Dopo aver appiattito l'elenco, esegui l'intersezione nel solito modo:


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
Questo è un bel po 'di appiattimento del codice Geo, ma non risponde alla domanda. Il richiedente si aspetta specificamente il risultato nella forma [[13,32], [7,13,28], [1,6]].
Rob Young,

8

Poiché è intersectstato definito, è sufficiente una comprensione di base dell'elenco:

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

Miglioramento grazie all'osservazione di S. Lott e all'osservazione associata di TM.:

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

5

Dato:

> 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]]

Trovo che il seguente codice funzioni bene e forse più conciso se si utilizza l'operazione set:

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

Ha ottenuto:

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

Se è necessario l'ordine:

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

noi abbiamo:

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

A proposito, per uno stile più pitone, anche questo va bene:

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

3

Non so se sono in ritardo nel rispondere alla tua domanda. Dopo aver letto la tua domanda, mi è venuta in mente una funzione intersect () che può funzionare sia sull'elenco che sull'elenco nidificato. Ho usato la ricorsione per definire questa funzione, è molto intuitiva. Spero che sia quello che stai cercando:

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

Esempio:

>>> 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

Pensi [1,2]di intersecarti [1, [2]]? Cioè, sono solo i numeri che ti interessano o anche la struttura dell'elenco?

Se solo i numeri, scopri come "appiattire" gli elenchi, quindi utilizza il set()metodo.


Vorrei lasciare invariata la struttura delle liste.
elfuego1,

1

Stavo anche cercando un modo per farlo, e alla fine è finito così:

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]

Se non usassi set.intersection, allora anche questi semplici liner sono quello che farei.
massacro del

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

Possiamo usare i metodi impostati per questo:

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

Per definire l'intersezione che tenga correttamente conto della cardinalità degli elementi utilizzare 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]] ?

Ecco un modo per impostare c3che non comporta set:

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

Ma se preferisci usare solo una riga, puoi farlo:

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

È una comprensione dell'elenco all'interno di una comprensione dell'elenco, il che è un po 'insolito, ma penso che non dovresti avere troppi problemi a seguirlo.


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]]

Per me questo è un modo molto elegante e veloce per farlo :)


0

elenco semplice può essere fatto reducefacilmente.

Tutto ciò che serve per utilizzare l' inizializzatore - terzo argomento nella reducefunzione.

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

Il codice sopra funziona sia per python2 che per python3, ma è necessario importare il modulo ridurre come from functools import reduce. Fare riferimento al link seguente per i dettagli.


-1

Modo semplice per trovare la differenza e l'intersezione tra iterabili

Utilizzare questo metodo se la ripetizione è 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.