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]
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 .
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
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).
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.
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
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()
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:
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`
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
Plus petit code:
def contains(a,b):
str(a)[1:-1].find(str(b)[1:-1])>=0
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
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)
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