선형 시간에서 가장 긴 공통 부분 문자열


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.

언어와 라이브러리

무료로 사용할 수있는 컴파일러 / 인터프리터 등이있는 모든 언어를 사용할 수 있습니다. 리눅스 용. 이 작업을 해결하도록 설계되지 않은 표준 오픈 소스 라이브러리 만 사용해야합니다. 분쟁이있는 경우이 언어를 표준 언어로 제공되거나 기본 저장소의 기본 우분투 컴퓨터에 설치할 수있는 라이브러리로 간주합니다.

유용한 정보

이 문제를 선형 시간으로 해결하는 데는 두 가지 방법이 있습니다. 하나는 먼저 접미사 트리를 계산하는 것이고, 두 번째는 먼저 접미사 배열과 LCP 배열을 계산하는 것입니다.


4
O(n) time가능합니까?
Savenkov Alexey

17
@Lembik 죄송합니다.이 알고리즘은 매우 복잡한 알고리즘이므로 100 줄 이상의 코드를 골라내는 것은 정말 재미 있지 않습니다.
FUZxxl

4
"유용한 정보"에서 제공하는 두 번째 링크의 기사에 따르면 "[접미사 트리]를 구성하려면 O (N ^ 2) 시간이 필요합니다"
KSFT

3
@Lembik 당신은 단지 가장 큰 표기법으로 최악의 경우를 가진 프로그램이이기는 곳에서 [가장 빠른 코드]를 만들어야합니다. 그렇다면 최소한 몇 가지 대답을 얻을 수 있으며 누군가 O (n)에서 해결할 있다고해도 그들은 이길 것입니다.
mbomb007

9
이것은 유효한 답변 당 가장 많이 삭제 된 답변이있는 질문이어야합니다.
FlipTack

답변:


39

파이썬 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

이것은 Kärkkäinen과 Sanders의 "Simple Linear Work Suffix Array Construction"에 설명 된 왜곡 알고리즘을 사용합니다. 이 문서에 포함 된 C ++ 구현은 이미 약간 "골프"느낌이 들지만 여전히 더 짧아 질 여지가 많습니다. 예를 들어, 우리는 O(n)요구 사항 을 위반하지 않고 종이처럼 단락 대신 길이가 1 인 배열에 도달 할 때까지 재귀 할 수 있습니다 .

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에 복사하고 6-9 행에서 2 칸을 삭제했지만 그것이 맞는지 확실하지 않습니다.
패트릭 로버츠

2
@PatrickRoberts 탭입니다.
Mitch Schwartz

2
좋은 대답입니다! 2016 년부터 새로운 선형 시간 SACA 인 GSACA를 살펴볼 수 있습니다. 참조 구현은 246 줄의 주석으로 가득 차 있으며 (코멘트없이 170 개) 매우 골프화가 가능합니다. github에서 찾을 수 있습니다.
Christoph

1
@MitchSchwartz 저는 현재 noPMO를 유지하려고 노력하고 있기 때문에 지금은 감정을 강하게 느낄 수 없습니다 (아마도 불균형 한 뇌 화학 물질로 인해). 코드를 빨리 읽을 때 구문 골프 모터가 그것을 발견했으며 특정 감정을 느끼는 것을 기억하지 못합니다. 당신은 같은 것을 또는 왜 질문을 생각 했습니까? :) 이제 궁금합니다.
Yytsi

1
@TuukkaX 예상치 못한 흥미로운 반응입니다. 글쎄, 나는 이것을 특별한 방법으로 표현 해야하는지 확실하지 않지만, 당신의 원래 의견이 실제로 정확하지 않다는 사실은 내가 물어보기로 한 이유에서 일부 역할을했습니다. :)
Mitch Schwartz의
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.