Лучший способ определить, находится ли последовательность в другой последовательности в Python
Это обобщение проблемы "строка содержит подстроку" для (более) произвольных типов.
Учитывая последовательность (например, список или кортеж), какой лучший способ определить, находится ли в ней другая последовательность? В качестве бонуса он должен вернуть индекс элемента, где начинается подпоследовательность:
Пример использования (последовательность в последовательности):
>>> seq_in_seq([5,6], [4,'a',3,5,6])
3
>>> seq_in_seq([5,7], [4,'a',3,5,6])
-1 # or None, or whatever
До сих пор я просто полагался на грубую силу, и это кажется медленным, уродливым и неуклюжим.
Ответы
Ответ 1
Я второй алгоритм Кнута-Морриса-Пратта. Кстати, ваша проблема (и решение KMP) - это точно рецепт 5.13 в Python Cookbook 2-е издание. Вы можете найти соответствующий код в http://code.activestate.com/recipes/117214/
Он находит все правильные подпоследовательности в заданной последовательности и должен использоваться как итератор:
>>> for s in KnuthMorrisPratt([4,'a',3,5,6], [5,6]): print s
3
>>> for s in KnuthMorrisPratt([4,'a',3,5,6], [5,7]): print s
(nothing)
Ответ 2
Здесь применяется подход грубой силы O(n*m)
(аналогичный @mcella answer). Это может быть быстрее, чем реализация алгоритма Knuth-Morris-Pratt в чистом Python O(n+m)
(см. @Gregg Lind answer) для небольших входных последовательностей.
#!/usr/bin/env python
def index(subseq, seq):
"""Return an index of `subseq`uence in the `seq`uence.
Or `-1` if `subseq` is not a subsequence of the `seq`.
The time complexity of the algorithm is O(n*m), where
n, m = len(seq), len(subseq)
>>> index([1,2], range(5))
1
>>> index(range(1, 6), range(5))
-1
>>> index(range(5), range(5))
0
>>> index([1,2], [0, 1, 0, 1, 2])
3
"""
i, n, m = -1, len(seq), len(subseq)
try:
while True:
i = seq.index(subseq[0], i + 1, n - m + 1)
if subseq == seq[i:i + m]:
return i
except ValueError:
return -1
if __name__ == '__main__':
import doctest; doctest.testmod()
Интересно, насколько велика величина в этом случае?
Ответ 3
То же, что и строка, соответствующая sir... соответствие последовательности Knuth-Morris-Pratt
Ответ 4
>>> def seq_in_seq(subseq, seq):
... while subseq[0] in seq:
... index = seq.index(subseq[0])
... if subseq == seq[index:index + len(subseq)]:
... return index
... else:
... seq = seq[index + 1:]
... else:
... return -1
...
>>> seq_in_seq([5,6], [4,'a',3,5,6])
3
>>> seq_in_seq([5,7], [4,'a',3,5,6])
-1
Извините, что я не эксперт по алгоритму, это самая быстрая вещь, о которой я могу думать в данный момент, по крайней мере, я думаю, что она выглядит красивой (мне), и мне было весело ее кодировать.; -)
Скорее всего, это то же самое, что и ваш подход к грубой силе.
Ответ 5
Простой подход: конвертировать в строки и полагаться на соответствие строк.
Пример использования списков строк:
>>> f = ["foo", "bar", "baz"]
>>> g = ["foo", "bar"]
>>> ff = str(f).strip("[]")
>>> gg = str(g).strip("[]")
>>> gg in ff
True
Пример с использованием кортежей строк:
>>> x = ("foo", "bar", "baz")
>>> y = ("bar", "baz")
>>> xx = str(x).strip("()")
>>> yy = str(y).strip("()")
>>> yy in xx
True
Пример использования списков чисел:
>>> f = [1 , 2, 3, 4, 5, 6, 7]
>>> g = [4, 5, 6]
>>> ff = str(f).strip("[]")
>>> gg = str(g).strip("[]")
>>> gg in ff
True
Ответ 6
Грубая сила может быть тонкой для небольших узоров.
Для более крупных рассмотрите алгоритм Aho-Corasick.
Ответ 7
Вот еще одна реализация KMP:
from itertools import tee
def seq_in_seq(seq1,seq2):
'''
Return the index where seq1 appears in seq2, or -1 if
seq1 is not in seq2, using the Knuth-Morris-Pratt algorithm
based heavily on code by Neale Pickett <[email protected]>
found at: woozle.org/~neale/src/python/kmp.py
>>> seq_in_seq(range(3),range(5))
0
>>> seq_in_seq(range(3)[-1:],range(5))
2
>>>seq_in_seq(range(6),range(5))
-1
'''
def compute_prefix_function(p):
m = len(p)
pi = [0] * m
k = 0
for q in xrange(1, m):
while k > 0 and p[k] != p[q]:
k = pi[k - 1]
if p[k] == p[q]:
k = k + 1
pi[q] = k
return pi
t,p = list(tee(seq2)[0]), list(tee(seq1)[0])
m,n = len(p),len(t)
pi = compute_prefix_function(p)
q = 0
for i in range(n):
while q > 0 and p[q] != t[i]:
q = pi[q - 1]
if p[q] == t[i]:
q = q + 1
if q == m:
return i - m + 1
return -1
Ответ 8
Я немного опаздываю на вечеринку, но здесь что-то простое, используя строки:
>>> def seq_in_seq(sub, full):
... f = ''.join([repr(d) for d in full]).replace("'", "")
... s = ''.join([repr(d) for d in sub]).replace("'", "")
... #return f.find(s) #<-- not reliable for finding indices in all cases
... return s in f
...
>>> seq_in_seq([5,6], [4,'a',3,5,6])
True
>>> seq_in_seq([5,7], [4,'a',3,5,6])
False
>>> seq_in_seq([4,'abc',33], [4,'abc',33,5,6])
True
Как отметил Илья В. Шуров, метод find не вернет правильные индексы с многосимвольными строками или многозначными числами.
Ответ 9
Другой подход, используя наборы:
set([5,6])== set([5,6])&set([4,'a',3,5,6])
True