Modo migliore per determinare se una sequenza è in un’altra sequenza in Python

Questa è una generalizzazione della “stringa contiene la sottostringa” problema (più) arbitrario tipi.

Data una sequenza (ad esempio un elenco o di una tupla), qual è il modo migliore per determinare se un’altra sequenza è all’interno di esso? Come bonus, si deve restituire l’indice dell’elemento in cui la sottosequenza inizia:

Esempio di utilizzo (Sequenza in Sequenza):

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

Finora, non mi fare affidamento solo sulla forza bruta e sembra lento, brutto e goffo.

InformationsquelleAutor Gregg Lind | 2009-01-08



9 Replies
  1. 20

    Io secondo il Knuth-Morris-Pratt algoritmo. A proposito, il tuo problema (e KMP soluzione) è esattamente la ricetta 5.13 Python Cookbook 2 ° edizione. È possibile trovare il codice relativo al http://code.activestate.com/recipes/117214/

    Trova tutti la corretta sottosequenze in una determinata sequenza, e dovrebbe essere usato come un iteratore:

    >>> 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)
    • Nota che il KMP attuazione dato sul codice.activestate era demostrably più lenta da 30-500 volte per alcuni (forse non rappresentativo di ingresso). Benchmarking per vedere se muto metodi built-in di superare sembra essere una buona idea!
    • KMP è noto per essere circa due volte più lento, come l’ingenuo algoritmo in pratica. Quindi, nella maggior parte dei casi è completamente inappropriato, nonostante la sua buona asintotica nel caso peggiore di runtime.
  2. 9

    Ecco un brute-force approccio O(n*m) (simile a @mcella risposta). Potrebbe essere più veloce di Knuth-Morris-Pratt l’implementazione dell’algoritmo in puro Python O(n+m) (vedi @Gregg Lind risposta) per piccolo sequenze di input.

    #!/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()

    Mi chiedo quanto grande è il piccolo in questo caso?

  3. 3

    Un approccio semplice: la Conversione di stringhe e si basano su stringa corrispondente.

    Esempio di utilizzo di liste di stringhe:

     >>> f = ["foo", "bar", "baz"]
     >>> g = ["foo", "bar"]
     >>> ff = str(f).strip("[]")
     >>> gg = str(g).strip("[]")
     >>> gg in ff
     True

    Esempio utilizzando le tuple di stringhe:

    >>> x = ("foo", "bar", "baz")
    >>> y = ("bar", "baz")
    >>> xx = str(x).strip("()")
    >>> yy = str(y).strip("()")
    >>> yy in xx
    True

    Esempio di utilizzo di liste di numeri:

    >>> f = [1 , 2, 3, 4, 5, 6, 7]
    >>> g = [4, 5, 6]
    >>> ff = str(f).strip("[]")
    >>> gg = str(g).strip("[]")
    >>> gg in ff
    True
    • Mi piace! Per quick & roba sporca, comunque. In generale: def is_in(seq1, seq2): return str(list(seq1))[1:-1] in str(list(seq2))[1:-1] Non è un buon modo per trovare l’indice della partita, credo.
  4. 2
    >>> 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

    Mi dispiace io non sono un algoritmo esperto, è solo la cosa più veloce la mia mente può pensare che al momento, almeno credo, sembra bello (per me) e mi sono divertito a scrivere codice. 😉

    Più probabilmente è la stessa cosa, il metodo della forza bruta sta facendo.

    • È bello e pulito, ma brute-forcy –> O(mn)
  5. 1

    Forza bruta può essere bene per piccoli modelli.

    Per quelli più grandi, guardare il Aho-Corasick algoritmo.

    • Aho-Corasick sarebbe grande. Io sono specificamente alla ricerca di python, o pythonish soluzioni… quindi se ci fosse un’implementazione, che sarebbe grande. Io curiosare.
  6. 1

    Qui è un altro KMP attuazione:

    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
    • Il tee chiamate, non sembrano essere un bene per nulla, perché l’altro elemento in tee, uscita 2-tupla viene ignorato. seq1 e seq2 di copiare due generatori, uno dei quali viene creata in un elenco, e l’altro che viene ignorato.
  7. 0

    Sono un po ‘ in ritardo alla festa, ma qui è qualcosa di semplice utilizzo di stringhe:

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


    Come notato da Ilya V. Schurov, il trovare metodo in questo caso non verrà restituito corretto con indici multi-stringhe di caratteri o multi-numeri a due cifre.

    • Questa soluzione non è affidabile in caso di elementi di sequenze non unico lenghs: è diventato, non è ovvio come tradurre indice restituito all’indice iniziale sequenze. Nota anche che backtick per `d` sintassi è deprecato come per Python 3 e scoraggiati.
    • esempio di non affidabilità anche con tutte le stesse dimensioni : sub=’ab’, completo=’aa’,’bb’
  8. -1

    Un altro approccio, utilizzando set:

    set([5,6])== set([5,6])&set([4,'a',3,5,6])
    True
    • Semplicemente scopre se l’insieme a è un sottoinsieme della sequenza. Non se è effettivamente in che ordine nella sequenza. set([5,6])== set([5,6])&set([4,'a',5,4,6]) restituisce True
    • che potrebbe essere un primo, veloce test però : controllare che tutti gli elementi sono in l’elenco completo.

Lascia un commento