web-dev-qa-db-fra.com

Tester si une liste contient une autre liste avec Python

Comment puis-je tester si une liste contient une autre liste (c'est-à-dire qu'il s'agit d'une sous-séquence contiguë). Disons qu'il y avait une fonction appelée contient:

contains([1,2], [-1, 0, 1, 2]) # Returns [2, 3] (contains returns [start, end])
contains([1,3], [-1, 0, 1, 2]) # Returns False
contains([1, 2], [[1, 2], 3]) # Returns False
contains([[1, 2]], [[1, 2], 3]) # Returns [0, 0]

Modifier:

contains([2, 1], [-1, 0, 1, 2]) # Returns False
contains([-1, 1, 2], [-1, 0, 1, 2]) # Returns False
contains([0, 1, 2], [-1, 0, 1, 2]) # Returns [1, 3]
41
None

Voici ma version:

def contains(small, big):
    for i in xrange(len(big)-len(small)+1):
        for j in xrange(len(small)):
            if big[i+j] != small[j]:
                break
        else:
            return i, i+len(small)
    return False

Il retourne un tuple de (début, fin + 1) puisque je pense que c'est plus Pythonique, comme le souligne Andrew Jaffe dans son commentaire. Il ne découpe pas de sous-listes et devrait donc être raisonnablement efficace.

Un point d’intérêt pour les débutants est qu’il utilise la clause else de la déclaration for - ce n’est pas quelque chose que j’utilise très souvent, mais qui peut être inestimable dans des situations comme celle-ci.

Ceci est identique à la recherche de sous-chaînes dans une chaîne. Ainsi, pour les grandes listes, il peut être plus efficace d'implémenter quelque chose comme l'algorithme Boyer-Moore .

16
Dave Kirby

Si tous les éléments sont uniques, vous pouvez utiliser des ensembles.

>>> items = set([-1, 0, 1, 2])
>>> set([1, 2]).issubset(items)
True
>>> set([1, 3]).issubset(items)
False
42
Thomas O

Il existe une fonction all() et any() pour le faire . Vérifier si list1 contient TOUS les éléments de list2

result = all(elem in list1 for elem in list2)

Pour vérifier si list1 contient N'IMPORTE QUEL élément dans list2

result = any(elem in list1 for elem in list2)

le résultat de la variable serait booléen (VRAI/FAUX).

10
ericyan3000

Permettez-moi de suggérer humblement l’algorithme Rabin-Karp si la liste big est vraiment longue. Le lien contient même du code presque utilisable dans presque Python.

4
9000

Après la modification de l'OP:

def contains(small, big):
    for i in xrange(1 + len(big) - len(small)):
        if small == big[i:i+len(small)]:
            return i, i + len(small) - 1
    return False
3
eumiro

Voici un algorithme simple qui utilise des méthodes de liste:

#!/usr/bin/env python

def list_find(what, where):
    """Find `what` list in the `where` list.

    Return index in `where` where `what` starts
    or -1 if no such index.

    >>> f = list_find
    >>> f([2, 1], [-1, 0, 1, 2])
    -1
    >>> f([-1, 1, 2], [-1, 0, 1, 2])
    -1
    >>> f([0, 1, 2], [-1, 0, 1, 2])
    1
    >>> f([1,2], [-1, 0, 1, 2])
    2
    >>> f([1,3], [-1, 0, 1, 2])
    -1
    >>> f([1, 2], [[1, 2], 3])
    -1
    >>> f([[1, 2]], [[1, 2], 3])
    0
    """
    if not what: # empty list is always found
        return 0
    try:
        index = 0
        while True:
            index = where.index(what[0], index)
            if where[index:index+len(what)] == what:
                return index # found
            index += 1 # try next position
    except ValueError:
        return -1 # not found

def contains(what, where):
    """Return [start, end+1] if found else empty list."""
    i = list_find(what, where)
    return [i, i + len(what)] if i >= 0 else [] #NOTE: bool([]) == False

if __name__=="__main__":
    import doctest; doctest.testmod()
1
jfs

Si nous précisons le problème concernant le fait de tester si une liste contient une autre liste avec une séquence, la réponse pourrait être la suivante:

def contains(subseq, inseq):
    return any(inseq[pos:pos + len(subseq)] == subseq for pos in range(0, len(inseq) - len(subseq) + 1))

Voici les tests unitaires que j'avais l'habitude de mettre au point:

https://Gist.github.com/anonymous/6910a85b4978daee137f

1
Oleksiy

Voici ma réponse. Cette fonction vous aidera à déterminer si B est une sous-liste de A. La complexité temporelle est O (n).

`def does_A_contain_B(A, B): #remember now A is the larger list
    b_size = len(B)
    for a_index in range(0, len(A)):
        if A[a_index : a_index+b_size]==B:
            return True
    else:
        return False`
1
Akila D. Perera

Cela fonctionne et est assez rapide car il effectue la recherche linéaire en utilisant la méthode intégrée list.index() et l'opérateur ==:

def contains(sub, pri):
    M, N = len(pri), len(sub)
    i, LAST = 0, M-N+1
    while True:
        try:
            found = pri.index(sub[0], i, LAST) # find first elem in sub
        except ValueError:
            return False
        if pri[found:found+N] == sub:
            return [found, found+N-1]
        else:
            i = found+1
1
martineau

Plus petit code:

def contains(a,b):
    str(a)[1:-1].find(str(b)[1:-1])>=0
1
Bart Mensfort

J'ai essayé de rendre cela aussi efficace que possible.

Il utilise un générateur; ceux qui ne sont pas familiers avec ces bêtes sont invités à vérifier leur documentation et celle de expressions de rendement .

Fondamentalement, il crée un générateur de valeurs à partir de la sous-séquence qui peut être réinitialisé en lui envoyant une valeur vraie. Si le générateur est réinitialisé, il commence à céder à partir du début de sub.

Ensuite, il ne fait que comparer les valeurs successives de sequence avec les rendements du générateur, en réinitialisant le générateur si elles ne correspondent pas.

Lorsque le générateur est à court de valeurs, c’est-à-dire qu’il atteint la fin de sub sans être réinitialisé, cela signifie que nous avons trouvé notre correspondance.

Comme cela fonctionne pour n'importe quelle séquence, vous pouvez même l'utiliser sur des chaînes, auquel cas il se comporte de la même manière que str.find, sauf qu'il renvoie False au lieu de -1.

Remarque supplémentaire: je pense que la deuxième valeur du Tuple retourné devrait, conformément aux normes Python, être normalement supérieure. c'est-à-dire "string"[0:2] == "st". Mais la spécification dit le contraire, alors c'est comme ça que ça marche.

Cela dépend s'il s'agit d'une routine à usage général ou si elle met en œuvre un objectif spécifique; Dans ce dernier cas, il serait peut-être préférable d'implémenter une routine polyvalente, puis de l'envelopper dans une fonction qui modifie la valeur de retour pour l'adapter à la spécification.

def reiterator(sub):
    """Yield elements of a sequence, resetting if sent ``True``."""
    it = iter(sub)
    while True:
        if (yield it.next()):
            it = iter(sub)

def find_in_sequence(sub, sequence):
    """Find a subsequence in a sequence.

    >>> find_in_sequence([2, 1], [-1, 0, 1, 2])
    False
    >>> find_in_sequence([-1, 1, 2], [-1, 0, 1, 2])
    False
    >>> find_in_sequence([0, 1, 2], [-1, 0, 1, 2])
    (1, 3)
    >>> find_in_sequence("subsequence",
    ...                  "This sequence contains a subsequence.")
    (25, 35)
    >>> find_in_sequence("subsequence", "This one doesn't.")
    False

    """
    start = None
    sub_items = reiterator(sub)
    sub_item = sub_items.next()
    for index, item in enumerate(sequence):
        if item == sub_item:
            if start is None: start = index
        else:
            start = None
        try:
            sub_item = sub_items.send(start is None)
        except StopIteration:
            # If the subsequence is depleted, we win!
            return (start, index)
    return False
0
intuited

Le problème de la plupart des réponses, c'est qu'elles conviennent aux éléments uniques de la liste. Si les éléments ne sont pas uniques et que vous souhaitez toujours savoir s'il existe une intersection, vous devez compter les éléments:

from collections import Counter as count

def listContains(l1, l2):
  list1 = count(l1)
  list2 = count(l2)

  return list1&list2 == list1

print( listContains([1,1,2,5], [1,2,3,5,1,2,1]) ) # Returns True
print( listContains([1,1,2,8], [1,2,3,5,1,2,1]) ) # Returns False

Vous pouvez également renvoyer l'intersection en utilisant ''.join(list1&list2)

0
mafonya

Je pense que celui-ci est rapide ...

def issublist(subList, myList, start=0):
    if not subList: return 0
    lenList, lensubList = len(myList), len(subList)
    try:
        while lenList - start >= lensubList:
            start = myList.index(subList[0], start)
            for i in xrange(lensubList):
                if myList[start+i] != subList[i]:
                    break
            else:
                return start, start + lensubList - 1
            start += 1
        return False
    except:
        return False
0
ChessMaster