web-dev-qa-db-fra.com

Comment vérifier si tous les éléments d'une liste sont présents dans une autre liste?

J'ai deux listes dites

List1 = ['a','c','c']
List2 = ['x','b','a','x','c','y','c']

Maintenant, je veux savoir si tous les éléments de List1 sont présents dans List2. Dans ce cas, tout y est. Je ne peux pas utiliser la fonction de sous-ensemble car je peux avoir des éléments répétés dans des listes. Je peux utiliser une boucle for pour compter le nombre d'occurrences de chaque élément dans List1 et voir s'il est inférieur ou égal au nombre d'occurrences dans List2. Y a-t-il une meilleure manière de faire cela?

Merci.

20
pogo

Lorsque le nombre d'occurrences n'a pas d'importance, vous pouvez toujours utiliser la fonctionnalité de sous-ensemble en créant un ensemble à la volée:

>>> list1 = ['a', 'c', 'c']
>>> list2 = ['x', 'b', 'a', 'x', 'c', 'y', 'c']
>>> set(list1) < set(list2)
True

Si vous devez vérifier si chaque élément apparaît au moins autant de fois dans la deuxième liste que dans la première liste, vous pouvez utiliser le type Counter et définir votre propre relation de sous-ensemble:

>>> from collections import Counter
>>> def counterSubset(list1, list2):
        c1, c2 = Counter(list1), Counter(list2)
        for k, n in c1.items():
            if n > c2[k]:
                return False
        return True

>>> counterSubset(list1, list2)
True
>>> counterSubset(list1 + ['a'], list2)
False
>>> counterSubset(list1 + ['z'], list2)
False

Si vous avez déjà des compteurs (ce qui peut être une alternative utile pour stocker vos données de toute façon), vous pouvez aussi simplement écrire ceci sur une seule ligne:

>>> all(n <= c2[k] for k, n in c1.items())
True
30
poke

Soyez conscient de ce qui suit:

>>>listA = ['a', 'a', 'b','b','b','c']
>>>listB = ['b', 'a','a','b','c','d']
>>>all(item in listB for item in listA)
True

Si vous lisez la ligne "all" comme vous le feriez en anglais, ceci n’est pas faux, mais peut être trompeur, comme listA a un troisième "b" mais listB n’en a pas. 

Cela a aussi le même problème:

def list1InList2(list1, list2):
    for item in list1:
        if item not in list2:
            return False
    return True

Juste une note. Ce qui suit ne fonctionne pas:

>>>tupA = (1,2,3,4,5,6,7,8,9)
>>>tupB = (1,2,3,4,5,6,6,7,8,9)
>>>set(tupA) < set(TupB)
False

Si vous convertissez les tuples en listes, cela ne fonctionne toujours pas. Je ne sais pas pourquoi les chaînes fonctionnent, mais pas les ints.

Fonctionne mais a le même problème de ne pas compter le nombre d'occurrences d'éléments:

>>>set(tupA).issubset(set(tupB))
True

L'utilisation d'ensembles n'est pas une solution complète pour la correspondance d'éléments multi-occurrences.

Mais voici une solution/adaptation unique à la réponse de shantanoo sans essayer/sauf:

all(True if sequenceA.count(item) <= sequenceB.count(item) else False for item in sequenceA)

Une fonction intégrée encapsulant une compréhension de liste en utilisant un opérateur conditionnel ternaire. Python est génial! Notez que le "<=" ne doit pas être "==".

Avec cette solution, les séquences A et B peuvent être du type Tuple et list et d’autres "séquences" avec des méthodes "comptage". Les éléments dans les deux séquences peuvent être la plupart des types. Je n'utiliserais pas cela avec dict tel qu'il est à présent, d'où l'utilisation de "séquence" au lieu de "itérable".

3
DevPlayer

Je ne peux pas utiliser la fonction de sous-ensemble car je peux avoir des éléments répétés dans des listes.

Cela signifie que vous souhaitez traiter vos listes comme multisets plutôt que ensembles. La manière habituelle de gérer les multisets en Python est avec collections.Counter :

Counter est une sous-classe dict permettant de compter les objets pouvant être hachés. Il s'agit d'une collection non ordonnée dans laquelle les éléments sont stockés sous forme de clés de dictionnaire et leur nombre en tant que valeurs de dictionnaire. Les comptes peuvent être n'importe quelle valeur entière, y compris des comptes nuls ou négatifs. La classe Counter est similaire aux sacs ou aux multisets dans d'autres langues.

Et, bien que vous pouvez implémentez un sous-ensemble pour les multisets (implémenté avec Counter) en bouclant et en comparant les comptes, comme dans la réponse de poke , ceci n'est pas nécessaire, de la même manière que vous {pouvez} sous-ensemble pour les ensembles (implémenté avec set ou frozenset) en boucle et en testant in, mais c'est inutile.

Le type Counter implémente déjà tous les opérateurs d'ensembles étendus de manière évidente pour les multisets.<1 Donc, vous pouvez simplement écrire un sous-ensemble en termes de ces opérateurs, et cela fonctionnera à la fois pour set et Counter dès l'installation.

Avec différence (multi) définie:2

def is_subset(c1, c2):
    return not c1 - c2

Ou avec intersection (multi) set:

def is_subset(c1, c2):
    def c1 & c2 == c1

1. Vous vous demandez peut-être pourquoi, si Counter implémente les opérateurs de l'ensemble, il ne fait pas que mettre en œuvre < et <= pour les sous-ensembles et les sous-ensembles appropriés. Bien que je ne puisse pas trouver le fil de courrier électronique, je suis à peu près sûr que cela a été discuté, et la réponse a été que "les opérateurs de l'ensemble" sont définis comme l'ensemble spécifique d'opérateurs défini dans la version initiale de collections.abc.Set (qui a depuis été développé , IIRC…), tous les opérateurs que set inclut par commodité, exactement de la même manière que Counter ne dispose pas de méthodes nommées comme intersection compatibles avec d'autres types que & uniquement parce que set l'a.

2. Cela dépend du fait que les collections en Python sont supposées être falsey quand elles sont vides et la vérité, sinon. Ceci est documenté ici pour les types prédéfinis, et le fait que les tests bool tombent en len est expliqué ici - mais il ne s'agit finalement que d'une convention, de sorte que des "quasi-collections" comme des tableaux numpy peuvent violer si elles ont une bonne raison. Cela vaut pour les "vraies collections" comme Counter, OrderedDict, etc. Si cela vous inquiète vraiment, vous pouvez écrire len(c1 - c2) == 0, mais notez que cela est contraire à l'esprit de PEP 8 .

0
abarnert

Une solution utilisant Counter et la méthode d'intersection intégrée (notez que - est une différence multiset correcte, pas une soustraction élément par élément):

from collections import Counter

def is_subset(l1, l2):
    c1, c2 = Counter(l1), Counter(l2)
    return not c1 - c2

Tester:

>>> List1 = ['a','c','c']
>>> List2 = ['x','b','a','x','c','y','c']
>>> is_subset(List1, List2)
True
0
fferri