Arhn - архитектура программирования

Лучший способ определить, входит ли последовательность в другую?

Это обобщение проблемы «строка содержит подстроку» на (более) произвольные типы.

Учитывая последовательность (например, список или кортеж), как лучше всего определить, находится ли внутри нее другая последовательность? В качестве бонуса он должен вернуть индекс элемента, с которого начинается подпоследовательность:

Пример использования (последовательность в последовательности):

>>> 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, 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)
08.01.2009
  • Обратите внимание, что реализация KMP, указанная в code.activestate, была заметно медленнее в 30-500 раз для некоторых (возможно, нерепрезентативных входных данных). Тестирование, чтобы увидеть, превосходят ли глупые встроенные методы по эффективности, кажется хорошей идеей! 09.01.2009
  • Известно, что на практике KMP примерно в два раза медленнее, чем наивный алгоритм. Следовательно, для большинства целей это совершенно неуместно, несмотря на хорошую асимптотику времени выполнения в худшем случае. 21.10.2010

  • 2

    Вот подход грубой силы O(n*m) (аналогичный ответ @ mcella). Это может быть быстрее, чем реализация алгоритма Кнута-Морриса-Пратта на чистом 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()
    

    Интересно, насколько велик маленький в этом случае?

    08.01.2009

    3

    Простой подход: преобразовать в строки и полагаться на сопоставление строк.

    Пример использования списков строк:

     >>> 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
    
    03.04.2015
  • Мне это нравится! Во всяком случае, для быстрых и грязных вещей. В общем: def is_in(seq1, seq2): return str(list(seq1))[1:-1] in str(list(seq2))[1:-1] Думаю, не лучший способ найти индекс совпадения. 21.09.2016

  • 4

    То же, что и сопоставление строк, сэр ... сопоставление строк Кнута-Морриса-Пратта

    08.01.2009

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

    Извините, я не эксперт по алгоритмам, это просто самая быстрая вещь, о которой я могу думать на данный момент, по крайней мере, я думаю, что это выглядит хорошо (для меня), и мне было весело его кодировать. ;-)

    Скорее всего, это то же самое, что и ваш метод грубой силы.

    08.01.2009
  • Хороший чистый, но грубый - ›O (mn) 08.01.2009

  • 6

    Для небольших шаблонов подходит грубая сила.

    Для более крупных обращайтесь к алгоритму Ахо-Корасика.

    08.01.2009
  • Ахо-Корасик было бы здорово. Я специально ищу питон или питонские решения ... так что, если бы была реализация, это было бы здорово. Я покопаюсь. 08.01.2009

  • 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
    
    08.01.2009
  • Вызовы tee кажутся бесполезными, поскольку другой элемент в выходном кортеже tee 2 игнорируется. seq1 и seq2 каждый копируется в два новых генератора, один из которых создается в списке, а другой игнорируется. 15.12.2018

  • 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 в этом случае не вернет правильные индексы с многосимвольными строками или многозначными числами.

    27.10.2016
  • Это решение ненадежно в случае, если элементы последовательностей имеют неуникальную длину: становится неочевидным, как преобразовать возвращаемый индекс в индекс в исходных последовательностях. Также обратите внимание, что обратная кавычка для синтаксиса `d` устарела, как и для Python 3, и не рекомендуется. 28.10.2016
  • пример ненадежности даже при всех одинаковых размерах: sub = 'ab', full = 'aa', 'bb' 07.07.2017

  • 9

    Другой подход, использующий наборы:

    set([5,6])== set([5,6])&set([4,'a',3,5,6])
    True
    
    03.12.2015
  • Просто выясняет, является ли набор подмножеством последовательности. Не в том ли это порядке в последовательности. set([5,6])== set([5,6])&set([4,'a',5,4,6]) возвращает True 14.02.2016
  • однако это может быть первым быстрым тестом: убедитесь, что все элементы находятся в полном списке. 07.07.2017

  • 10

    Как бы то ни было, я пробовал использовать такую ​​двухстороннюю очередь:

    from collections import deque
    from itertools import islice
    
    def seq_in_seq(needle, haystack):
        """Generator of indices where needle is found in haystack."""
        needle = deque(needle)
        haystack = iter(haystack)  # Works with iterators/streams!
        length = len(needle)
        # Deque will automatically call deque.popleft() after deque.append()
        # with the `maxlen` set equal to the needle length.
        window = deque(islice(haystack, length), maxlen=length)
        if needle == window:
            yield 0  # Match at the start of the haystack.
        for index, value in enumerate(haystack, start=1):
            window.append(value)
            if needle == window:
                yield index
    

    Одним из преимуществ реализации двухсторонней очереди является то, что она выполняет только один линейный проход по стогу сена. Поэтому, если стог сена транслируется, он все равно будет работать (в отличие от решений, основанных на нарезке).

    Решение по-прежнему - грубая сила, O (n * m). Некоторые простые локальные тесты показали, что он был примерно в 100 раз медленнее, чем C-реализация поиска по строкам в str.index.

    30.12.2020
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..