Come posso verificare se un elenco è un sottoinsieme di un altro?


185

Devo verificare se un elenco è un sottoinsieme di un altro: un ritorno booleano è tutto ciò che cerco.

Testare l'uguaglianza nella lista più piccola dopo un'intersezione è il modo più veloce per farlo? Le prestazioni sono della massima importanza dato il numero di set di dati che devono essere confrontati.

Aggiunta di ulteriori fatti basati su discussioni:

  1. Una delle liste sarà la stessa per molti test? Fa come uno di questi è una tabella di ricerca statica.

  2. Deve essere un elenco? Non lo è: la tabella di ricerca statica può essere qualsiasi cosa abbia le prestazioni migliori. Quello dinamico è un dict dal quale estraiamo le chiavi per eseguire una ricerca statica.

Quale sarebbe la soluzione ottimale dato lo scenario?


Dici della velocità, forse l'intorpidimento sarebbe utile, a seconda del tuo utilizzo.
ninMonkey,

2
Le voci dell'elenco sono cancellabili?
wim,

2

hai bisogno di un sottoinsieme adeguato o possono essere uguali?
törzsmókus,

2
Perché non impostare (list_a) .issubset (set (list_b))?
SeF

Risposte:


127

La funzione performante fornita da Python è questa set.issubset. Ha alcune restrizioni che lo rendono poco chiaro se è la risposta alla tua domanda, tuttavia.

Un elenco può contenere elementi più volte e ha un ordine specifico. Un set no. Inoltre, i set funzionano solo su oggetti hash .

Stai chiedendo del sottoinsieme o sottosequenza (il che significa che vorrai un algoritmo di ricerca di stringhe)? Una delle liste sarà la stessa per molti test? Quali sono i tipi di dati contenuti nell'elenco? E del resto, deve essere un elenco?

Gli altri tuoi post si intersecano in un dict e un elenco ha reso i tipi più chiari e ha ricevuto una raccomandazione per utilizzare le visualizzazioni dei tasti del dizionario per la loro funzionalità simile a un set. In quel caso si sapeva che funzionava perché le chiavi del dizionario si comportano come un set (tant'è che prima avevamo set in Python abbiamo usato i dizionari). Ci si chiede come il problema sia diventato meno specifico in tre ore.


Mi riferisco solo a un sottoinsieme e issubset si comporta bene - Grazie. Tuttavia, sono curioso di sapere 2 domande qui. 1. Una delle liste sarà la stessa per molti test? Fa come una di esse è una tabella di ricerca statica 2. Deve essere un elenco? Non lo è: la tabella di ricerca statica può essere qualsiasi cosa abbia le prestazioni migliori. Quello dinamico è un dict dal quale estraiamo le chiavi per eseguire una ricerca statica. Questo fatto modificherà la soluzione?
IUnnown

Non tanto. Le chiavi di un dizionario sono simili a un set e sono già disposte in una tabella hash, quindi l'utilizzo di un set per la parte statica non comporterà ulteriori complicazioni. Fondamentalmente il fatto che uno sia un dict significa che potrebbe non essere necessario convertire la parte statica in un set (è possibile controllare tutto (itertools.imap (dict.has_key, mylist)) con prestazioni O (n)).
Yann Vernier,

Non capisco come questa (o qualsiasi altra soluzione basata sui set) possa essere la risposta accettata qui. La domanda riguarda le liste e francamente penso che il sottoinsieme in "verifica se una lista è un sottoinsieme dell'altra" non deve essere preso alla lettera. Al momento della conversione in set, qualsiasi informazione sugli elementi duplicati viene persa, tuttavia, se l'elenco iniziale potrebbe contenere quelli, potrebbe essere importante controllare se compaiono nel secondo elenco e dire veramente che tutti gli elementi di un elenco possono essere trovati nell'altro. I set non lo fanno!
inVader,

Il contesto è importante; questo è stato accettato per aiutare chi chiede e ha spiegato la distinzione. Ci è stato detto che i candidati sarebbero stati rappresentabili come set, quindi era un compito fisso. Il tuo caso potrebbe essere diverso e la differenza menzionata verrebbe risolta utilizzando multiset come le raccolte. Contatore.
Yann Vernier,

141
>>> a = [1, 3, 5]
>>> b = [1, 3, 5, 8]
>>> c = [3, 5, 9]
>>> set(a) <= set(b)
True
>>> set(c) <= set(b)
False

>>> a = ['yes', 'no', 'hmm']
>>> b = ['yes', 'no', 'hmm', 'well']
>>> c = ['sorry', 'no', 'hmm']
>>> 
>>> set(a) <= set(b)
True
>>> set(c) <= set(b)
False

21
Questo sembra più bello e scrive più semplice, ma il più veloce dovrebbe essere set(a).issubset(b) perché in questo caso si converte solo ain set ma non b, il che consente di risparmiare tempo. È possibile utilizzare timeitper confrontare il tempo impiegato in due comandi. Ad esempio, timeit.repeat('set(a)<set(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000) e timeit.repeat('set(a).issubset(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000)
Yulan Liu,

8
@YulanLiu: Odio interromperti, ma la prima cosa da issubsetfare è controllare se l'argomento è un set/ frozenset, e se non lo è, lo converte in un setconfronto temporaneo , esegue il controllo, quindi getta via il temporaneo set. Le differenze di tempistica (se presenti) sarebbero un fattore di piccole differenze nei costi di ricerca LEGB (trovare setuna seconda volta è più costoso della ricerca di attributi su una esistente set), ma è soprattutto un lavaggio per input abbastanza grandi.
ShadowRanger il

3
Se entrambi l'elenco contiene gli stessi valori, allora questo restituirà false, la condizione dovrebbe essere impostata (a) <= set (b) invece
ssi-anik

2
Come può questa risposta essere corretta? Ha chiesto un elenco non un set. Sono completamente diversi. Che cosa succede se a = [1, 3, 3, 5, 5] eb = [1, 3, 3, 3, 5]. La teoria degli insiemi non è appropriata per i duplicati.
Eamonn Kenny,

1
Vorrei anche sottolineare che se a = [1,3,5] eb = [1,3,5], set (a) <set (b) restituirà False. È possibile aggiungere l'operatore uguale a gestire questi casi: ovvero set (a) <= set (b).
Jon,

37
one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

all(x in two for x in one)

Spiegazione: Generatore che crea booleani eseguendo il ciclo attraverso l'elenco per oneverificare se quell'elemento è nell'elenco two. all()ritorna Truese ogni articolo è vero, altrimenti False.

C'è anche un vantaggio che allrestituisce False nella prima istanza di un elemento mancante piuttosto che dover elaborare ogni elemento.


Penso per leggibilità ed essere esplicito di ciò che stai cercando di ottenere, set(one).issubset(set(two))è un'ottima soluzione. Con la soluzione che ho pubblicato dovresti essere in grado di usarlo con qualsiasi oggetto se hanno gli operatori di confronto corretti definiti.
voidnologo,

4
Utilizzare un'espressione del generatore, non una comprensione dell'elenco; il primo consentirà alldi cortocircuitare correttamente, il secondo eseguirà tutti i controlli anche se dal primo controllo risulterebbe chiaro che il test avrebbe avuto esito negativo. Basta rilasciare le parentesi quadre per ottenere all(x in two for x in one).
ShadowRanger il

Sbaglio o non puoi usare questo metodo con la gente del posto?
Homper il

22

Supponendo che gli articoli siano lavabili

>>> from collections import Counter
>>> not Counter([1, 2]) - Counter([1])
False
>>> not Counter([1, 2]) - Counter([1, 2])
True
>>> not Counter([1, 2, 2]) - Counter([1, 2])
False

Se non ti interessano gli oggetti duplicati, ad es. [1, 2, 2]e [1, 2]quindi basta usare:

>>> set([1, 2, 2]).issubset([1, 2])
True

Testare l'uguaglianza nella lista più piccola dopo un'intersezione è il modo più veloce per farlo?

.issubsetsarà il modo più veloce per farlo. Controllare la lunghezza prima del test issubsetnon migliorerà la velocità perché hai ancora elementi O (N + M) da scorrere e controllare.


6

Un'altra soluzione sarebbe quella di utilizzare a intersection.

one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

set(one).intersection(set(two)) == set(one)

L'intersezione degli insiemi dovrebbe contenere set one

(O)

one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

set(one) & (set(two)) == set(one)

2
one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

set(x in two for x in one) == set([True])

Se list1 è nell'elenco 2:

  • (x in two for x in one)genera un elenco di True.

  • quando facciamo a set(x in two for x in one)ha un solo elemento (Vero).


2

La teoria degli insiemi non è appropriata per gli elenchi poiché i duplicati daranno risposte errate usando la teoria degli insiemi.

Per esempio:

a = [1, 3, 3, 3, 5]
b = [1, 3, 3, 4, 5]
set(b) > set(a)

non ha significato. Sì, dà una risposta falsa ma questo non è corretto poiché la teoria degli insiemi sta solo confrontando: 1,3,5 contro 1,3,4,5. Devi includere tutti i duplicati.

Invece è necessario contare ogni occorrenza di ciascun elemento ed eseguire un controllo maggiore di uguale a. Questo non è molto costoso, perché non utilizza operazioni O (N ^ 2) e non richiede un ordinamento rapido.

#!/usr/bin/env python

from collections import Counter

def containedInFirst(a, b):
  a_count = Counter(a)
  b_count = Counter(b)
  for key in b_count:
    if a_count.has_key(key) == False:
      return False
    if b_count[key] > a_count[key]:
      return False
  return True


a = [1, 3, 3, 3, 5]
b = [1, 3, 3, 4, 5]
print "b in a: ", containedInFirst(a, b)

a = [1, 3, 3, 3, 4, 4, 5]
b = [1, 3, 3, 4, 5]
print "b in a: ", containedInFirst(a, b)

Quindi eseguendo questo ottieni:

$ python contained.py 
b in a:  False
b in a:  True

1

Dal momento che nessuno ha considerato di confrontare due stringhe, ecco la mia proposta.

Ovviamente potresti voler controllare se la pipe ("|") non fa parte di nessuna delle due liste e forse hai scelto automaticamente un altro carattere, ma hai avuto l'idea.

L'uso di una stringa vuota come separatore non è una soluzione poiché i numeri possono avere più cifre ([12,3]! = [1,23])

def issublist(l1,l2):
    return '|'.join([str(i) for i in l1]) in '|'.join([str(i) for i in l2])

0

Scusami se sono in ritardo alla festa. ;)

Per verificare se uno set Aè un sottoinsieme di set B, Pythonhas A.issubset(B)and A <= B. Funziona setsolo e funziona benissimo, ma la complessità dell'implementazione interna è sconosciuta. Riferimento: https://docs.python.org/2/library/sets.html#set-objects

Ho escogitato un algoritmo per verificare se list Aè un sottoinsieme delle list Bseguenti osservazioni.

  • Per ridurre la complessità nella ricerca di un sottoinsieme, trovo appropriato sortentrambi gli elenchi prima di confrontare gli elementi per qualificarsi per il sottoinsieme.
  • Mi ha aiutato breakal loopcui valore elemento della seconda lista B[j]è maggiore del valore di elemento della prima lista A[i].
  • last_index_jviene utilizzato per ricominciare loopda list Bdove era stato interrotto l'ultima volta. Aiuta a evitare di avviare il confronto dall'inizio del list B(che è, come si può immaginare inutili, a cominciare list Bda index 0in successiva iterations.)
  • La complessità sarà O(n ln n)ciascuna per l'ordinamento di entrambi gli elenchi e O(n)per il controllo del sottoinsieme.
    O(n ln n) + O(n ln n) + O(n) = O(n ln n).

  • Il codice ha molte printdichiarazioni per vedere cosa sta succedendo in ognuna iterationdelle loop. Questi sono solo per la comprensione.

Controlla se un elenco è un sottoinsieme di un altro elenco

is_subset = True;

A = [9, 3, 11, 1, 7, 2];
B = [11, 4, 6, 2, 15, 1, 9, 8, 5, 3];

print(A, B);

# skip checking if list A has elements more than list B
if len(A) > len(B):
    is_subset = False;
else:
    # complexity of sorting using quicksort or merge sort: O(n ln n)
    # use best sorting algorithm available to minimize complexity
    A.sort();
    B.sort();

    print(A, B);

    # complexity: O(n^2)
    # for a in A:
    #   if a not in B:
    #       is_subset = False;
    #       break;

    # complexity: O(n)
    is_found = False;
    last_index_j = 0;

    for i in range(len(A)):
        for j in range(last_index_j, len(B)):
            is_found = False;

            print("i=" + str(i) + ", j=" + str(j) + ", " + str(A[i]) + "==" + str(B[j]) + "?");

            if B[j] <= A[i]:
                if A[i] == B[j]:
                    is_found = True;
                last_index_j = j;
            else:
                is_found = False;
                break;

            if is_found:
                print("Found: " + str(A[i]));
                last_index_j = last_index_j + 1;
                break;
            else:
                print("Not found: " + str(A[i]));

        if is_found == False:
            is_subset = False;
            break;

print("subset") if is_subset else print("not subset");

Produzione

[9, 3, 11, 1, 7, 2] [11, 4, 6, 2, 15, 1, 9, 8, 5, 3]
[1, 2, 3, 7, 9, 11] [1, 2, 3, 4, 5, 6, 8, 9, 11, 15]
i=0, j=0, 1==1?
Found: 1
i=1, j=1, 2==1?
Not found: 2
i=1, j=2, 2==2?
Found: 2
i=2, j=3, 3==3?
Found: 3
i=3, j=4, 7==4?
Not found: 7
i=3, j=5, 7==5?
Not found: 7
i=3, j=6, 7==6?
Not found: 7
i=3, j=7, 7==8?
not subset

Se li
ordini

0

Il codice seguente verifica se un determinato set è un "sottoinsieme corretto" di un altro set

 def is_proper_subset(set, superset):
     return all(x in superset for x in set) and len(set)<len(superset)


Grazie @YannVernier Ho modificato per includere i controlli vuoti sia per il sottoinsieme che per il superset in modo che ritorni falso quando entrambi sono vuoti.
Leo Bastin,

Ma perché lo fai? Per A essere un sottoinsieme di B significa semplicemente che A non contiene elementi che non sono in B, o equivalentemente, tutti gli elementi in A sono anche in B. Il set vuoto è quindi un sottoinsieme di tutti i set, incluso se stesso. I tuoi controlli extra affermano che non lo è, e affermi che questo è in qualche modo ideale, ma è contrario alla terminologia stabilita. Qual è il vantaggio?
Yann Vernier,

Grazie @YannVernier Ora il codice controlla se un determinato set è un "sottoinsieme corretto" di un altro set.
Leo Bastin

Questo è tanto negativo quanto le risposte basate sull'uso di set . Mentre matematicamente parlando, un insieme è una raccolta di elementi distinti, possiamo e non dobbiamo fare affidamento su tale presupposto quando controlliamo se un elenco fa parte di un altro. Se l'elenco iniziale dovesse contenere duplicati, la funzione potrebbe comunque restituire True , anche se l'elemento in questione è presente nel secondo elenco solo una volta. Non penso che questo sia il comportamento corretto quando si tenta di confrontare elenchi.
inVader,

0

In Python 3.5 puoi fare un [*set()][index]per ottenere l'elemento. È una soluzione molto più lenta rispetto ad altri metodi.

one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

result = set(x in two for x in one)

[*result][0] == True

o solo con len e set

len(set(a+b)) == len(set(a))

0

Ecco come so se un elenco è un sottoinsieme di un altro, la sequenza conta per me nel mio caso.

def is_subset(list_long,list_short):
    short_length = len(list_short)
    subset_list = []
    for i in range(len(list_long)-short_length+1):
        subset_list.append(list_long[i:i+short_length])
    if list_short in subset_list:
        return True
    else: return False

0

La maggior parte delle soluzioni ritiene che gli elenchi non abbiano duplicati. Nel caso in cui le tue liste abbiano duplicati puoi provare questo:

def isSubList(subList,mlist):
    uniqueElements=set(subList)
    for e in uniqueElements:
        if subList.count(e) > mlist.count(e):
            return False     
    # It is sublist
    return True

Assicura che l'elenco secondario non abbia mai elementi diversi dall'elenco o da una quantità maggiore di un elemento comune.

lst=[1,2,2,3,4]
sl1=[2,2,3]
sl2=[2,2,2]
sl3=[2,5]

print(isSubList(sl1,lst)) # True
print(isSubList(sl2,lst)) # False
print(isSubList(sl3,lst)) # False

-1

Se stai chiedendo se un elenco è "contenuto" in un altro elenco, allora:

>>>if listA in listB: return True

Se stai chiedendo se ogni elemento in listA ha un numero uguale di elementi corrispondenti in listB prova:

all(True if listA.count(item) <= listB.count(item) else False for item in listA)

Questo non funziona per me. Restituisce false anche se listA == listB
cass

@cass Ho provato solo con le stringhe. Prova questo sul tuo computer. pastebin.com/9whnDYq4
DevPlayer

Mi riferivo alla parte "if listA in listB: return True", non alla seconda parte.
Cass

@cass Considerare: ['one', 'two'] in ['one', 'two'] produce False. ['one', 'two'] in ['one', 'two', 'three'] produce False. ['one', 'two'] in [['one', 'two'], 'three'] produce True. Quindi sì, se listA == ListB, listA in listB restituirà sempre False perché listA dovrebbe essere un elemento list all'interno di listB. Forse stai pensando: listA in listB significa "Gli elementi in listA sono elencati come list in elenco B. Non è questo il significato di listA in listB
DevPlayer

@cass Ah, vedo come il mio post confonde. Il post originale chiedeva di verificare che l'elenco A fosse un sottoinsieme dell'elencoB. Tecnicamente il mio post è sbagliato in base alla domanda del post originale. Perché sia ​​giusto, la domanda avrebbe dovuto chiedere "if listA in [item0, item2, listA, item3, listA,]". Non "elementi in ['a', 'b', 'c'] in ['d', 'c', 'f', 'a', 'b', 'a']".
DevPlayer,

-2

Se a2 is subset of a1alloraLength of set(a1 + a2) == Length of set(a1)

a1 = [1, 2, 3, 4, 5]
a2 = [1, 2, 3]

len(set(a1)) == len(set(a1 + a2))
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.