Comment puis-je vérifier si une liste est un sous-ensemble d'une autre?


185

Je dois vérifier si une liste est un sous-ensemble d'une autre - un retour booléen est tout ce que je recherche.

Tester l'égalité sur la plus petite liste après une intersection est-il le moyen le plus rapide de le faire? Les performances sont de la plus haute importance étant donné le nombre d'ensembles de données à comparer.

Ajout de faits supplémentaires en fonction des discussions:

  1. L'une ou l'autre des listes sera-t-elle la même pour de nombreux tests? Il fait comme l'un d'eux est une table de recherche statique.

  2. Doit-il être une liste? Ce n'est pas le cas - la table de recherche statique peut être tout ce qui fonctionne le mieux. Le dynamique est un dict à partir duquel nous extrayons les clés pour effectuer une recherche statique.

Quelle serait la solution optimale compte tenu du scénario?


Vous parlez de vitesse, peut-être que numpy serait utile, en fonction de votre utilisation.
ninMonkey

2
Les éléments de la liste sont-ils hachables?
wim


avez-vous besoin d'un sous-ensemble approprié, ou peuvent-ils être égaux?
törzsmókus

2
Pourquoi ne pas définir (list_a) .issubset (set (list_b))?
SeF

Réponses:


127

La fonction performante fournie par Python pour cela est set.issubset. Il comporte cependant quelques restrictions qui ne permettent pas de savoir si c'est la réponse à votre question.

Une liste peut contenir des éléments plusieurs fois et a un ordre spécifique. Un ensemble ne le fait pas. De plus, les ensembles ne fonctionnent que sur les objets hachables .

Demandez-vous un sous-ensemble ou une sous-séquence (ce qui signifie que vous voudrez un algorithme de recherche de chaîne)? L'une ou l'autre des listes sera-t-elle la même pour de nombreux tests? Quels sont les types de données contenus dans la liste? Et d'ailleurs, faut-il que ce soit une liste?

Votre autre article croise un dict et une liste a rendu les types plus clairs et a obtenu une recommandation d'utiliser les vues clés du dictionnaire pour leurs fonctionnalités de type ensemble. Dans ce cas, il était connu pour fonctionner parce que les clés de dictionnaire se comportent comme un ensemble (à tel point qu'avant d'avoir des ensembles en Python, nous utilisions des dictionnaires). On se demande comment la question est devenue moins précise en trois heures.


Je fais référence à un sous-ensemble uniquement et issubset fonctionne très bien - Merci. Cependant, je suis curieux de savoir 2 questions ici. 1. L'une ou l'autre des listes sera-t-elle la même pour de nombreux tests? Il fait comme l'un d'eux est une table de recherche statique 2.Doit-il être une liste? Ce n'est pas le cas - la table de recherche statique peut être tout ce qui fonctionne le mieux. Le dynamique est un dict à partir duquel nous extrayons les clés pour effectuer une recherche statique. Ce fait modifiera-t-il la solution?
IUnknown

Pas tant. Les clés d'un dictionnaire sont semblables à des ensembles et sont déjà disposées dans une table de hachage, et par conséquent, l'utilisation d'un ensemble pour la partie statique n'entraînera pas de complications supplémentaires. Fondamentalement, le fait que l'un soit un dict signifie que vous n'aurez peut-être pas besoin de convertir la partie statique en un ensemble (vous pouvez tout vérifier (itertools.imap (dict.has_key, mylist)) avec les performances O (n)).
Yann Vernier

Je ne comprends pas comment cela (ou toute autre solution reposant sur des ensembles) peut être la réponse acceptée ici. La question porte sur les listes et je pense franchement que le sous-ensemble dans "vérifier si une liste est un sous-ensemble de l'autre" ne doit pas être pris à la lettre. Lors de la conversion en ensembles, toute information sur les éléments en double est perdue, mais si la liste initiale peut les contenir, il peut être important de vérifier si elles apparaissent dans la deuxième liste également pour dire vraiment que tous les éléments d'une liste peuvent être trouvés dans l'autre. Les sets ne font pas ça!
inVader

Le contexte compte; cela a été accepté pour aider le demandeur, et a expliqué la distinction. On nous a dit que les candidats seraient représentables sous forme d'ensembles, donc c'était une tâche bien définie. Votre cas peut être différent, et la différence que vous mentionnez sera résolue à l'aide de multisets tels que les collections.
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
Cela semble le plus joli et écrit le plus simple, mais le plus rapide devrait être set(a).issubset(b) parce que dans ce cas, vous ne convertissez qu'en aset mais pas b, ce qui fait gagner du temps. Vous pouvez utiliser timeitpour comparer le temps consommé dans deux commandes. Par exemple, timeit.repeat('set(a)<set(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000) et timeit.repeat('set(a).issubset(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000)
Yulan Liu

8
@YulanLiu: Je déteste vous le casser, mais la toute première chose à issubsetfaire est de vérifier si l'argument est un set/ frozenset, et si ce n'est pas le cas, il le convertit en temporaire setpour comparaison, exécute la vérification, puis jette le temporaire set. Les différences de temps (le cas échéant) seraient un facteur de petites différences dans les coûts de recherche LEGB (trouver setune deuxième fois est plus cher que la recherche d'attribut sur un existant set), mais c'est surtout un lavage pour des entrées suffisamment grandes.
ShadowRanger

3
Si les deux listes contiennent les mêmes valeurs, alors celle-ci retournera false, la condition doit être définie (a) <= set (b) à la place
ssi-anik

2
Comment cette réponse peut-elle être correcte. Il a demandé une liste, pas un ensemble. Ils sont complètement différents. Et si a = [1, 3, 3, 5, 5] et b = [1, 3, 3, 3, 5]. La théorie des ensembles est inappropriée pour les doublons.
Eamonn Kenny

1
Je tiens également à souligner que si a = [1,3,5] et b = [1,3,5], set (a) <set (b) renverra False. Vous pouvez ajouter l'opérateur égal pour gérer ces cas: ie 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)

Explication: Générateur créant des booléens en parcourant la liste en onevérifiant si cet élément est dans la liste two. all()renvoie Truesi chaque élément est véridique, sinon False.

Il y a aussi un avantage qui allrenvoie False sur la première instance d'un élément manquant plutôt que d'avoir à traiter chaque élément.


Je pense que pour la lisibilité et être explicite de ce que vous essayez de réaliser, set(one).issubset(set(two))c'est une excellente solution. Avec la solution que j'ai publiée, vous devriez pouvoir l'utiliser avec tous les objets s'ils ont les bons opérateurs de comparaison définis.
voidnologo

4
Utilisez une expression de générateur, pas une compréhension de liste; le premier permettra allde se court-circuiter correctement, le second effectuera tous les contrôles même s'il serait clair dès le premier contrôle que le test échouerait. Laissez simplement tomber les crochets pour obtenir all(x in two for x in one).
ShadowRanger

Est-ce que je me trompe ou vous ne pouvez pas utiliser cette méthode avec les locaux?
Homper

22

En supposant que les éléments sont hachables

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

Si vous ne vous souciez pas des éléments en double, par exemple. [1, 2, 2]et [1, 2]puis il suffit d' utiliser:

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

Tester l'égalité sur la plus petite liste après une intersection est-il le moyen le plus rapide de le faire?

.issubsetsera le moyen le plus rapide de le faire. Vérifier la longueur avant le test issubsetn'améliorera pas la vitesse car vous avez encore des éléments O (N + M) à parcourir et à vérifier.


6

Une autre solution serait d'utiliser un fichier intersection.

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

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

L'intersection des ensembles contiendrait de set one

(OU)

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

Si list1 est dans la liste 2:

  • (x in two for x in one)génère une liste de True.

  • quand on fait a un set(x in two for x in one)seul élément (True).


2

La théorie des ensembles est inappropriée pour les listes car les doublons entraîneront de mauvaises réponses en utilisant la théorie des ensembles.

Par exemple:

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

n'a pas de sens. Oui, cela donne une fausse réponse, mais ce n'est pas correct car la théorie des ensembles ne fait que comparer: 1,3,5 contre 1,3,4,5. Vous devez inclure tous les doublons.

Au lieu de cela, vous devez compter chaque occurrence de chaque élément et effectuer une vérification supérieure à égale. Ce n'est pas très coûteux, car il n'utilise pas d'opérations O (N ^ 2) et ne nécessite pas de tri rapide.

#!/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)

Ensuite, en exécutant ceci, vous obtenez:

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

1

Puisque personne n'a envisagé de comparer deux chaînes, voici ma proposition.

Vous pouvez bien sûr vouloir vérifier si le tube ("|") ne fait pas partie de l'une ou l'autre des listes et peut-être choisi automatiquement un autre caractère, mais vous avez eu l'idée.

Utiliser une chaîne vide comme séparateur n'est pas une solution car les nombres peuvent avoir plusieurs chiffres ([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

Pardonnez-moi si je suis en retard à la fête. ;)

Pour vérifier si l'un set Aest un sous-ensemble de set B, Pythona A.issubset(B)et A <= B. Cela fonctionne setuniquement et fonctionne très bien MAIS la complexité de la mise en œuvre interne est inconnue. Référence: https://docs.python.org/2/library/sets.html#set-objects

Je suis venu avec un algorithme pour vérifier s'il list As'agit d'un sous-ensemble des list Bremarques suivantes.

  • Pour réduire la complexité de la recherche d'un sous-ensemble, je trouve qu'il est approprié de sortcommencer par les deux listes avant de comparer les éléments pour se qualifier pour un sous-ensemble.
  • Il m'a aidé à breakla looplorsque la valeur de l' élément de deuxième liste B[j]est supérieure à la valeur de l' élément de la première liste A[i].
  • last_index_jest utilisé pour démarrer loopsur list Boù il avait laissé la dernière. Cela permet d'éviter de commencer les comparaisons depuis le début de list B(ce qui est, comme vous pourriez le deviner inutile, de commencer list Bpar la index 0suite iterations.)
  • La complexité sera à la O(n ln n)fois pour le tri des deux listes et O(n)pour la vérification du sous-ensemble.
    O(n ln n) + O(n ln n) + O(n) = O(n ln n).

  • Le code contient de nombreuses printinstructions pour voir ce qui se passe à chacun iterationdes loop. Ceux-ci sont uniquement destinés à la compréhension.

Vérifiez si une liste est un sous-ensemble d'une autre liste

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

Production

[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

Si vous les triez, il n'y a plus aucune raison d'utiliser une liste au lieu d'un ensemble…
LtWorf

0

Le code ci-dessous vérifie si un ensemble donné est un "sous-ensemble approprié" d'un autre ensemble

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


Merci @YannVernier J'ai modifié pour inclure des vérifications vides pour le sous-ensemble et le sur-ensemble afin qu'il retourne false lorsque les deux sont vides.
Leo Bastin

Mais pourquoi faites-vous cela? Pour A, être un sous-ensemble de B signifie simplement que A ne contient aucun élément qui ne soit pas dans B, ou de manière équivalente, tous les éléments de A sont également dans B. L'ensemble vide est donc un sous-ensemble de tous les ensembles, y compris lui-même. Vos vérifications supplémentaires affirment que ce n'est pas le cas, et vous affirmez que c'est en quelque sorte idéal, mais c'est contraire à la terminologie établie. Quel est l'avantage?
Yann Vernier

Merci @YannVernier Maintenant, le code vérifie si un ensemble donné est un "sous-ensemble approprié" d'un autre ensemble.
Leo Bastin

C'est aussi mauvais que les réponses reposant sur l'utilisation d' ensembles . Bien que mathématiquement parlant, un ensemble soit une collection d'éléments distincts, nous pouvons et ne devons pas nous fier à cette hypothèse pour vérifier si une liste fait partie d'une autre. Si la liste initiale devait contenir des doublons, votre fonction pourrait toujours renvoyer True , même si l'élément en question n'est présent qu'une seule fois dans la deuxième liste. Je ne pense pas que ce soit le bon comportement lorsque vous essayez de comparer des listes.
inVader

0

En python 3.5, vous pouvez faire un [*set()][index]pour obtenir l'élément. C'est une solution beaucoup plus lente que les autres méthodes.

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

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

[*result][0] == True

ou juste avec len et set

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

0

Voici comment je sais si une liste est un sous-ensemble d'une autre, la séquence m'importe dans mon cas.

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 plupart des solutions considèrent que les listes ne comportent pas de doublons. Si vos listes ont des doublons, vous pouvez essayer ceci:

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

Cela garantit que la sous-liste n'a jamais d'éléments différents d'une liste ou d'une plus grande quantité d'un élément commun.

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

Si vous demandez si une liste est "contenue" dans une autre liste, alors:

>>>if listA in listB: return True

Si vous demandez si chaque élément de listA a un nombre égal d'éléments correspondants dans listB, essayez:

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

Cela ne fonctionne pas pour moi. Renvoie false même si listA == listB
cass

@cass Je n'ai testé qu'avec des chaînes. Essayez ceci sur votre machine. pastebin.com/9whnDYq4
DevPlayer

Je faisais référence à la partie "if listA in listB: return True", pas à la deuxième partie.
cass

@cass Considérons: ['one', 'two'] in ['one', 'two'] renvoie False. ['un', 'deux'] dans ['un', 'deux', 'trois'] donne False. ['un', 'deux'] dans [['un', 'deux'], 'trois'] donne Vrai. Donc oui si listA == ListB alors listA dans listB retournera toujours False car listA devrait être un élément de list dans listB. Vous pensez peut-être: listA dans listB signifie «Les éléments de listA sont-ils répertoriés comme éléments de listB. Ce n'est pas le sens de listA dans listB
DevPlayer

@cass Ah, je vois comment mon message est déroutant. Le message d'origine demandait de tester pour listA étant un sous-ensemble de listB. Techniquement, mon message est erroné en fonction de la question du message d'origine. Pour qu'elle soit correcte, la question aurait dû demander "si listA in [item0, item2, listA, item3, listA,]". Pas "éléments dans ['a', 'b', 'c'] dans ['d', 'c', 'f', 'a', 'b', 'a']".
DevPlayer

-2

Si a2 is subset of a1, alorsLength 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))
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.