Найдовша загальна підрядка за лінійним часом


45

Ця задача полягає у написанні коду для вирішення наступної проблеми.

Враховуючи два рядки A і B, ваш код повинен виводити початкові та кінцеві індекси підрядка A з такими властивостями.

  • Підрядка A також повинна відповідати деякій підрядковій лінії B.
  • Більше не повинно бути підрядків A, що задовольняє першому властивості.

Наприклад:

A = xxxappleyyyyyyy

B = zapplezzz

Підрядка appleз індексами 4 8(індексування від 1) буде дійсним результатом.

Функціональність

Ви можете припустити, що введення буде стандартним в або у файлі в локальному каталозі, це ваш вибір. Формат файлу буде просто двома рядками, розділеними новим рядком. Відповідь має бути повноцінною програмою, а не лише функцією.

Зрештою, я хотів би перевірити ваш код на двох підрядках, узятих із рядків у http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ .

Оцінка

Це кодовий гольф із поворотом. Ваш код повинен бути запущений у O(n)часі, де nзагальна довжина вводу.

Мови та бібліотеки

Ви можете використовувати будь-яку мову, у якій є доступний компілятор / перекладач / тощо. для Linux. Вам слід використовувати лише стандартні бібліотеки з відкритим кодом, не призначені для вирішення цього завдання. У разі суперечок я буду вважати це будь-якою бібліотекою, яка є стандартною для вашої мови, або бібліотекою, яку ви можете встановити на машину ubuntu за замовчуванням із сховища за замовчуванням.

Корисна інформація

Існує як мінімум два способи вирішити цю проблему за лінійним часом. Одне - спочатку обчислити дерево суфіксів, а друге - спочатку обчислити масив суфіксів та масив LCP.


4
O(n) timeВи впевнені, що це можливо?
Савенков Олексій

17
@ Лембік Вибачте, але це дуже складні алгоритми, і це не дуже цікаво, щоб передати 100+ рядків коду.
FUZxxl

4
У статті на другому посиланні, яку ви надаєте у розділі "Корисна інформація", йдеться про те, що "для побудови [дерева суфікса] потрібен час O (N ^ 2)"
KSFT

3
@Lembik Вам слід просто поставити питання [найшвидший-код], де перемагає програма з найкращим гіршим випадком у нотації big-oh. Тоді ви принаймні отримаєте кілька відповідей, і навіть якщо хтось зможе це вирішити в O (n), вони виграють.
mbomb007

9
Це має бути питання з найбільш видаленими відповідями на дійсну відповідь ...
FlipTack

Відповіді:


39

Python 2, 646 байт

G=range;w=raw_input;z=L,m,h=[0]*3
s=w();t=len(s);s+='!%s#'%w();u=len(s);I=z*u
def f(s,n):
 def r(o):
    b=[[]for _ in s];c=[]
    for x in B[:N]:b[s[x+o]]+=x,
    map(c.extend,b);B[:N]=c
 M=N=n--~n/3;t=n%3%2;B=G(n+t);del B[::3];r(2);u=m=p=r(1)>r(0);N-=n/3
 for x in B*1:v=s[x:x+3];m+=u<v;u=v;B[x/3+x%3/2*N]=m
 A=1/M*z or f(B+z,M)+z;B=[x*3for x in A if x<N];J=I[r(0):n];C=G(n)
 for k in C:b=A[t]/N;a=i,j=A[t]%N*3-~b,B[p];q=p<N<(s[i:i-~b],J[i/3+b+N-b*N])>(s[j+t/M:j-~b],J[j/3+b*N]);C[k]=x=a[q];I[x]=k;p+=q;t+=1-q
 return C
S=f(map(ord,s)+z*40,u)
for i in G(u):
 h-=h>0;j=S[I[i]-1]
 while s[i+h]==s[j+h]:h+=1
 if(i<t)==(t<j)<=h>m:m=h;L=min(i,j)
print-~L,L+m

Для цього використовується алгоритм перекосу, описаний у "Простій лінійній конструкції масиву суфіксів робіт" Керккяйнена та Сандерса. Реалізація C ++, що входить до цього документу, вже відчуває себе трохи «гостро», але є ще достатньо місця для скорочення. Наприклад, ми можемо повторювати, поки не досягнемо масиву довжини один, замість короткого замикання, як у папері, не порушуючи O(n)вимоги.

У частині LCP я дотримувався "Обчислення лінійного часу найдовшого поширеного префікса в масивах суфіксів та його застосувань" від Kusai et al.

Програма виводить, 1 0якщо найдовша загальна підрядка порожня.

Ось який-небудь код розробки, який включає в себе більш ранню версію програми, яка слідує впровадженню C ++ трохи ближче, кілька повільніших підходів для порівняння та простий генератор тестових випадків:

from random import *

def brute(a,b):
    L=R=m=0

    for i in range(len(a)):
        for j in range(i+m+1,len(a)+1):
            if a[i:j] in b:
                m=j-i
                L,R=i,j

    return L+1,R

def suffix_array_slow(s):
    S=[]
    for i in range(len(s)):
        S+=[(s[i:],i)]
    S.sort()
    return [x[1] for x in S]

def slow1(a,b):
    # slow suffix array, slow lcp

    s=a+'!'+b
    S=suffix_array_slow(s)

    L=R=m=0

    for i in range(1,len(S)):
        x=S[i-1]
        y=S[i]
        p=s[x:]+'#'
        q=s[y:]+'$'
        h=0
        while p[h]==q[h]:
            h+=1
        if h>m and len(a)==sorted([x,y,len(a)])[1]:
            m=h
            L=min(x,y)
            R=L+h

    return L+1,R

def verify(a,b,L,R):
    if L<1 or R>len(a) or a[L-1:R] not in b:
        return 0
    LL,RR=brute(a,b)
    return R-L==RR-LL

def rand_string():
    if randint(0,1):
        n=randint(0,8)
    else:
        n=randint(0,24)
    a='zyxwvutsrq'[:randint(1,10)]
    s=''
    for _ in range(n):
        s+=choice(a)
    return s

def stress_test(f):
    numtrials=2000
    for trial in range(numtrials):
        a=rand_string()
        b=rand_string()
        L,R=f(a,b)
        if not verify(a,b,L,R):
            LL,RR=brute(a,b)
            print 'failed on',(a,b)
            print 'expected:',LL,RR
            print 'actual:',L,R
            return
    print 'ok'

def slow2(a,b):
    # slow suffix array, linear lcp

    s=a+'!'+b+'#'
    S=suffix_array_slow(s)

    I=S*1
    for i in range(len(S)):
        I[S[i]]=i

    L=R=m=h=0

    for i in range(len(S)):
        if I[i]:
            j=S[I[i]-1]
            while s[i+h]==s[j+h]:
                h+=1
            if h>m and len(a)==sorted([i,j,len(a)])[1]:
                m=h
                L=min(i,j)
                R=L+h
            h-=h>0

    return L+1,R

def suffix_array(s,K):
    # skew algorithm

    n=len(s)
    s+=[0]*3
    n0=(n+2)/3
    n1=(n+1)/3
    n2=n/3
    n02=n0+n2
    adj=n0-n1

    def radix_pass(a,o,n=n02):
        c=[0]*(K+3)
        for x in a[:n]:
            c[s[x+o]+1]+=1
        for i in range(K+3):
            c[i]+=c[i-1]
        for x in a[:n]:
            j=s[x+o]
            a[c[j]]=x
            c[j]+=1

    A=[x for x in range(n+adj) if x%3]+[0]*3

    radix_pass(A,2)
    radix_pass(A,1)
    radix_pass(A,0)

    B=[0]*n02
    t=m=0

    for x in A[:n02]:
        u=s[x:x+3]
        m+=t<u
        t=u
        B[x/3+x%3/2*n0]=m

    A[:n02]=1/n02*[0]or suffix_array(B,m)
    I=A*1
    for i in range(n02):
        I[A[i]]=i+1

    B=[3*x for x in A if x<n0]
    radix_pass(B,0,n0)

    R=[]

    p=0
    t=adj
    while t<n02:
        x=A[t]
        b=x>=n0
        i=(x-b*n0)*3-~b
        j=B[p]
        if p==n0 or ((s[i:i+2],I[A[t]-n0+1])<(s[j:j+2],I[j/3+n0]) if b else (s[i],I[A[t]+n0])<(s[j],I[j/3])):R+=i,;t+=1
        else:R+=j,;p+=1

    return R+B[p:n0]

def solve(a,b):
    # linear

    s=a+'!'+b+'#'
    S=suffix_array(map(ord,s),128)

    I=S*1
    for i in range(len(S)):
        I[S[i]]=i

    L=R=m=h=0

    for i in range(len(S)):
        if I[i]:
            j=S[I[i]-1]
            while s[i+h]==s[j+h]:
                h+=1
            if h>m and len(a)==sorted([i,j,len(a)])[1]:
                m=h
                L=min(i,j)
                R=L+h
            h-=h>0

    return L+1,R

stress_test(solve)

1
Виправте мене, якщо я помиляюся, але чи це насправді не 739 байт? Я скопіював у mothereff.in/byte-counter і видалив 2 пробіли з рядків 6-9, але я не впевнений, що це правильно.
Патрік Робертс

2
@PatrickRoberts Це вкладки.
Мітч Шварц

2
Гарна відповідь! Ви можете поглянути на GSACA - новий лінійний час SACA з 2016 р. Довідкова реалізація - 246 рядків, повних коментарів (170 без коментарів) і здається дуже поважною. Ви знайдете його на github.
Крістоф

1
@MitchSchwartz На даний момент я намагаюся залишатися на noPMO, тому зараз не можу відчувати емоцій (можливо, через неврівноважені хімічні речовини в мозку). Під час швидкого читання коду мій синтаксичний моторна гольф помітив це, і я не пам'ятаю, щоб відчувати якісь конкретні емоції. Ви думали про одне й те саме чи чому питання? :) Зараз мені цікаво.
Yytsi

1
@TuukkaX Це цікава відповідь, яку я не очікував. Ну, я не впевнений, чи варто це якимось чином формулювати, але те, що ваш оригінальний коментар насправді був невірним, зіграв певну роль у тому, чому я вирішив запитати. :)
Мітч Шварц,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.