브래킷 균형


24

목표 : 괄호 문자열이 주어지면 입력 문자열을 괄호가 균형을 이루는 문자열로 바꾸는 데 필요한 최소 Damerau-Levenshtein 거리를 출력합니다 .

입력

입력 문자열은 대괄호 만 포함하고 다른 문자는 포함하지 않습니다. 즉,의 문자를 조합 한 것입니다 (){}[]<>. 문자열 또는 문자 배열로 입력 할 수 있습니다. 입력 문자열에 대해 다른 가정을 할 수 없습니다. 언어가 지원하는 최대 크기까지 임의로 길거나 비어 있거나 대괄호가 이미 균형을 이룰 수 있습니다.

Damerau-Levenshtein 거리

Damerau-Levenshtein 두 문자열 사이의 거리는 두 개의 인접한 문자의 삽입, 삭제, 단일 문자 대체 및 전치 (스와핑)의 최소 수입니다.

산출

출력은 입력 문자열과 대괄호가 일치하는 문자열 사이의 최소 Damerau-Levenshtein 거리 여야합니다. 결과는 균형 잡힌 문자열이 아닌 숫자 여야 합니다.

여는 괄호와 닫는 괄호가 올바른 순서이고 괄호 안에 문자가없는 경우 괄호 쌍은 "일치하는"것으로 간주됩니다.

()
[]{}

또는 내부의 모든 하위 요소도 일치하는 경우

[()()()()]
{<[]>}
(()())

하위 요소는 여러 층으로 중첩 될 수도 있습니다.

[(){<><>[()]}<>()]
<[{((()))}]>

(정의를 위해 @DJMcMayhem에게 감사합니다)

테스트 사례

Input                   Possible Balanced       Output

Empty                   Empty                   0
[](){}<>                [](){}<>                0           
[(){}<>                 [(){}<>]                1           
[(])                    []()                    1           
[[[[[[[[                [][][][]                4
(](<>}[>(}>><(>(({}]    ()(<>)[(<><>){}]        7
>]{])<                  []{()}                  3
([)}}>[                 (){}<>                  4
{<((<<][{{}>[<)         <>(<<[]>{}>[])          5
{><({((})>}}}{(}}       {<><({()})>}{}{()}      4
(](<)>}[>(}>>{]<<(]]    (<()<><<>()>>[])<()>    9
}})(                    {}()                    2

(테스트 사례의 절반을 해결해 주신 @WheatWizard에게 감사드립니다)

이것은 이며 가장 적은 바이트 수입니다!

제출물은 테스트 가능해야합니다. 즉, 각 테스트 사례에 대한 결과가 1 시간 이내에 출력되어야합니다.


9
자신의 대괄호 균형 : P
Christopher

3
우리가이 도전에 대한 하나의 올바른 비 브루투스 해답을 보게된다면 놀랄 것입니다.
orlp

5
@SIGSEGV 그 대답은 그것은 당신처럼 균형 여부를 중요하지 않습니다 1 인 [<>]또는 []<>또는<>
나단 메릴

3
@Bijan Nah, 훨씬 쉽게 만들 수 없으며 Brain-Flak의 생일이 곧 올 것입니다!
Pavel

3
@qwr 왜 제한이 있습니까? 시간 제한은 주어진 테스트 사례에만 적용되며, 대규모 입력의 경우 프로그램이 전세계에서 항상 시간이 걸릴 수 있습니다.
Pavel

답변:


13

망막 254 252 264 248 240 232 267 바이트

버그를 지적 해준 @AnthonyPham, @officialaimm 및 @MistahFiggins에 감사합니다

T`[]()`:;'"
+`'-*"|:-*;|{-*}|<-*>
-
+`'(\W+)"|:(\W+);|{(\W+)}|<(\W+)>
A$1$2$3$+B
+`'(\D+)"|:(\D+);|{(\D+)}|<(\D+)>
6$1$2$3$+9
(.*)(}{|"'|;:|><)
1$1
-

A6B9|6A9B
1
A6+B9+|A6+.B9+.|A+6.B+9
11
T`':{";}`<<<>
(.*)(<\W|\W>)
1$1
+`<(.*A.*B.*)?\W|\W(.*A.*B.*)?>
1$1$2
\W|6B|1

온라인으로 사용해보십시오!

무 브루 포스 솔루션! 모든 테스트 사례에서 작동하며 하나의 오류도 발견했습니다.

@MartinEnder ( ${4}- $+) 덕분에 -2 바이트

추가 교체 사례를 설명하기 위해 +12 바이트

문자 클래스를 더 잘 활용하여 -16 바이트

스와핑에 대한 불필요한 제한을 제거하여 -8 바이트 이것은 또한 버그를 수정했습니다 :)

스와핑 로직을 단일 정규식으로 결합하여 -10 바이트

연속 스왑을 설명하기 위해 +2 바이트

+ 다양한 버그 수정이 가능합니다 **

설명:

T`[]()`:;'"편의상 특수 브래킷 유형을 교체하는 데 사용됩니다. 첫째, 우리는 재귀와 일치하는 모든 브래킷을 교체 -,AB 또는) 69인접 여부에 따라 .

그런 다음 새로 일치하는 대괄호를 제거하고 1 하고 문자열의 시작 부분에 를 . 우리는 또한 교체 -가 바로 위의 교환을 위해 사용되면서 빈 문자열.

다음으로 이미 일치하는 대괄호와 겹치지 않는 일치하지 않는 대괄호 쌍을 제거하고 "대체"를 시도합니다. 1 하고 문자열에 를 합니다.

마지막으로 \W|6B|1 남은 단일 괄호에 1s 수를 더한 수를 계산합니다 .

** 현재 Retina의 회선 분할 기능을 사용하는 더 짧은 버전을 작업 중이지만 상당한 문제가 발생하여 시간이 오래 걸릴 수 있습니다.


${4}단축 할 수있다 $+. 왜 이것이 작동하는지 궁금합니다.
Martin Ender

@MartinEnder 감사합니다! 나는 그것이 항상 작동 하는지 확실 하지는 않지만 적어도 제공된 테스트 케이스와 내가 생각해 낸 몇 가지 에지 케이스에 대해 작동합니다.
math junkie

2
주어진 첫 번째 괄호 안의 내부를 [{][}] [] [[][][][][][]] [][][][][][][][][][][][]간단히 바꾸고 }균형을 잡을 수 있습니다. 간격은 입력을 좀 더 읽기 쉽게 만드는 데 사용됩니다. 3을 출력했지만 실제로는 1이어야합니다.
Anthony Pham

@AnthonyPham 감사합니다! 나는 그것이 왜 작동하지 않는지 알고 있다고 생각한다. 나는 그것을 고칠 수있는 영리한 방법을 찾으려고 노력할 것이다
math junkie

더 이상한 것은 [{]}1 을 반환하지만 [{][]}2 를 반환 한다는 것입니다 .
Anthony Pham

12

Brain-Flak , 1350 바이트

{({}(())(<>))<>({(()()()())<{({}[()])<>}{}>}{}<>({<({}[()])>{()(<{}>)}}{}{}<>))<>}<>([[]]){([[]({}()<>)]<>)<>{(({}())<<>(({})<(({}(<()>))<>({}))([(())()()]){<>({}())}{}{<>{}<>({}()){(((({}<(({}<>)<{({}()<([(){}])>)}{}>)<>(({}(<>))<{({}()<([(){}])>)}{}<>>)><>({}))(<(((({}({})[()])[()()]<>({}))<>[({})({}){}]({}<>))<>[(({}<>)<>({}<>)<>)])<>>)))[()](<()>)<<>(({})<({}{}()){({}()<({}<>)<>>)}{}<>(({})<<>(({}<>))>)<>(())>){({}[()()]<(<([({[{}]<(({})()<>[({})]<>)>{()(<{}>)}}{}<(({})<>[()({}<(({}<<>({}<>)<>(({})<>)>)<>[(){}])<>>)]<>)>{()(<{}>)}{}(){[()](<{}>)}<<>{({}<>)<>}{}>)]({}{}))>)<>{({}<>)<>}>)}{}{}<>{}{}{({}<>)<>}{}{}(<>)<>{({}<>)<>}{}{(<{}>)<>{({}<>)<>}<>({}<{}>){({}<>)<>}}{}((({}<({}({})({})<{{}<>{}(<>)}{}(((({}<({}<>)>)<>)))<>>)<>>)<><({}<({}<<>(()())>)>)>)<<>({}<{}{({}<>)([()()()]){((({}()()<>))[()]<(({()(<{}>)}{})<>({}<(({}<<>({}[()()](()[({})({})]({[()](<{}>)}{}<>{}<(({})<>)>)<>))>)<>)>)<>)<>({}<({}<({}<({}<>)>)>)>)>)}{}{}<>}<>{}{}{}{}{}{}{}{}>)>)>)}{}({}<({}<{({}<(({}){({}())}{}{}<(({}){({}())}{}{}<>)>)>)<>}<>{((({}(()()){([{}](<({}(<()>)<>){({}<({}<>)>(())<>)}{}>({})<<>{{}({}<>)<>}{}>))([{}()]{})}{})))<>(({}))<>{<>({}[()])}{}({}<<>{}{}{<>}>)<>{}}<>(({}<>){[()](<{}>)}{})(<>)>)>)<>(<({}<>)>)<>}<>{}({}<(({}){({}())}{}{}){({}<({}<>)>(())<>)}{}{}>)<>{{}({}<>)<>}{}>)<>>)}{}<>([[]{}])}{}(([]){<{}{}>([])}{}<>){({}[()]<{}>)}{}({}<>)

온라인으로 사용해보십시오!

등속 비교 및 ​​포인터 역 참조에서이 알고리즘은 O (n 3 )입니다. 불행히도 Brain-Flak은 이들 중 어느 것도 가지고 있지 않으므로이 프로그램은 대신 O (n 5 ) 시간에 실행됩니다. 가장 긴 테스트 케이스는 약 15 분이 걸립니다.

결과 단순화

내 알고리즘이 작동하는지 확인하려면 검색 공간을 상당히 줄인 일부 결과를 표시해야합니다. 이 결과는 대상이 하나의 특정 문자열이 아닌 전체 언어라는 사실에 의존합니다.

  • 삽입이 필요하지 않습니다. 대신 삽입 된 문자가 결국 일치하는 대괄호를 제거하면됩니다.

  • 브래킷을 제거 할 필요가 없으며 두 개의 이웃을 교체하십시오. 이를 확인하려면, 제거 된 브라켓이라고 wlog 가정 (우리가 변화되도록, a(cca두 단계. c사본 을 변경 하고 삽입 ca()하면 스왑없이 두 단계를 수행 할 수 있습니다 . 이 삽입은 위 규칙에 따라 제거 할 수 있습니다.

  • 동일한 브래킷을 두 번 교체 할 필요는 없습니다. 이것은 일반적으로 Damerau-Levenshtein 거리에 대한 표준 사실입니다.

내가 사용하지 않은 또 다른 단순화 된 결과는 비용이 많이 들기 때문에

  • 두 개의 대괄호가 서로 바뀌고 서로 일치하지 않는 경우 각 대괄호와의 최종 일치는 절대 변경되거나 바뀌지 않습니다.

알고리즘

문자열이 균형 잡힌 문자열로 축소되면 다음 중 하나에 해당됩니다.

  • 첫 번째 괄호가 삭제됩니다.
  • 첫 번째 브래킷은 원래 위치에 머무르고 특정 위치에서 브래킷과 일치합니다 k(둘 중 하나 또는 둘 다를 변경 한 후).
  • 첫 번째 브래킷은 두 번째 브래킷과 교체되며 위치의 브래킷과 일치합니다 k.

두 번째 경우, 위치에있는 브래킷 k이 이웃 중 하나와 교체되었을 수 있습니다. 후자의 두 경우 중 어느 경우 든 (새로) 첫 번째 대괄호와 위치에서 시작된 대괄호 사이의 문자열은 k균형 잡힌 문자열로 편집해야합니다 k.

이것은 동적 프로그래밍 접근법이 사용될 수 있음을 의미합니다. 교체 된 대괄호는 다시 교체 할 필요가 없기 때문에 연속 된 하위 문자열뿐만 아니라 두 번째 문자 및 / 또는 두 번째 문자를 해당 하위 문자열에서 제거하여 형성된 하위 시퀀스 만 고려하면됩니다. 따라서 우리가 볼 필요 가있는 O (n 2 ) 서브 시퀀스 만 있습니다. 이들 각각은 첫 번째 대괄호와 일치하는 O (n) 가능한 방법을 가지고 있으므로 알고리즘은 O (n 3 위의 조건 )입니다.

데이터 구조

오른쪽 스택에는 원래 문자열의 대괄호와 대괄호 당 2 바이트가 포함됩니다. 첫 번째 항목은 전체 대괄호를 결정하고 일치하는 대괄호는 정확히 1의 차이를 갖도록 선택됩니다. 두 번째 항목은 여는 대괄호인지 닫는 대괄호인지 만 결정합니다. 두 대괄호가 일치하는 데 걸리는 횟수를 결정합니다. 서로. 이 아래의 암시 적 0은 명시 적으로 만들어지지 않으므로 사용할 수 있습니다.[] 문자열의 총 길이를 얻는 데 .

고려중인 각 하위 문자열은 0에서 2n 사이의 두 숫자로 표시됩니다. 하나는 시작 위치와 다른 하나입니다. 해석은 다음과 같습니다.

  • 에서 시작하는 하위 문자열 2k은 위치 k(0 색인) 에서 시작하며 두 번째 문자는 제거되지 않습니다.
  • 에서 시작하는 하위 문자열 2k+1은 position k에서 시작 하고 두 번째 문자는 왼쪽으로 교체되어 제거됩니다.
  • 로 끝나는 부분 문자열 2k은 위치 직전에 종료 됩니다 k(즉, 범위는 왼쪽과 오른쪽을 포함합니다).
  • 로 끝나는 부분 문자열 2k-1은 position 직전에 종료되며 k두 번째 문자는 바르게 교체되어 제거됩니다.

일부 범위 ( k~ k+1, 2k+1~ 2k+1, 2k+1~ 2k+32k+1~ 2k+5)는 물리적으로 의미가 없습니다. 어쨌든 그 중 일부는 중간 값으로 표시되는데,이를 방지하기 위해 추가 검사를 추가하는 것보다 쉽습니다.

왼쪽 스택에는 각 하위 문자열을 균형 잡힌 문자열로 변환하는 데 필요한 편집 횟수가 저장됩니다. 간격의 편집 거리는 (x,y)깊이에 저장됩니다 x + y(y-1)/2.

내부 루프 중에 왼쪽 스택 위에 항목이 추가되어 어떤 이동이 가능한지 나타냅니다. 이 항목의 길이는 5 바이트입니다. 위에서 계산, 숫자는 d+1, y1, x1, y2, x2, 움직임은 비용 어디에 d편집 단계 및 분할에 문자열 (x1,y1)(x2,y2).

코드

올 설명입니다. 지금은 작업 코드 사본입니다. 일부 의견은 용어와 일치하지 않을 수 있습니다.

# Determine bracket type for each byte of input
{({}(())(<>))<>({(()()()())<{({}[()])<>}{}>}{}<>({<({}[()])>{()(<{}>)}}{}{}<>))<>}

# For every possible interval length:
<>([[]]){

  # Compute actual length
  ([[]({}()<>)]<>)

  # Note: switching stacks in this loop costs only 2 bytes.
  # For each starting position:
  # Update/save position and length
  <>{(({}())<<>(({})<

    # Get endpoints
    (({}(<()>))<>({}))

    # If length more than 3:
    ([(())()()]){<>({}())}{}{

      # Clean up length-3 left over from comparison
      <>{}<>

      # Initialize counter at 2
      # This counter will be 1 in the loop if we're using a swap at the beginning, 0 otherwise
      ({}())

      # For each counter value:
      {

        # Decrement counter and put on third stack
        (((({}<

          # Do mod 2 for end position
          (({}<>)<{({}()<([(){}])>)}{}>)<>

          # Do mod 2 for start position
          (({}(<>))<{({}()<([(){}])>)}{}<>>)

        # Subtract 1 from counter if swap already happened
        ><>({}))(<

          # Compute start position of substrings to consider
          (((({}({})[()])[()()]<>({}))

            # Compute start position of matches to consider
            <>[({})({}){}]({}<>))<>

            # Compute end position of matches to consider
            [(({}<>)<>({}<>)<>)]

          # Push total distance of matches
          )

        # Push counter as base cost of moves
        # Also push additional copy to deal with length 5 intervals starting with an even number
        <>>)))[()](<()>)<

          # With match distance on stack
          <>(({})<

            # Move to location in input data
            ({}{}()){({}()<({}<>)<>>)}{}

            # Make copy of opening bracket to match
            <>(({})<<>(({}<>))>)

          # Mark as first comparison (swap allowed)
          <>(())>)

          # For each bracket to match with:
          {({}[()()]<

            (<([(

              # If swap is allowed in this position:
              {

                # Subtract 1 from cost
                [{}]

                # Add 1 back if swap doesn't perfectly match
                <(({})()<>[({})]<>)>{()(<{}>)}

              }{}

              # Shift copy of first bracket over, while computing differences
              <(({})<>[()({}<(({}<<>({}<>)<>(({})<>)>)<>[(){}])<>>)]<>)>

              # Add 1 if not perfectly matched
              {()(<{}>)}{}

              # Add 1 if neither bracket faces the other
              # Keep 0 on stack to return here
              (){[()](<{}>)}

              # Return to start of brackets
              <<>{({}<>)<>}{}>

            # Add to base cost and place under base cost
            )]({}{}))>)

            # Return to spot in brackets
            # Zero here means swap not allowed for next bracket
            <>{({}<>)<>}

          >)}

          # Cleanup and move everything to right stack
          {}{}<>{}{}{({}<>)<>}{}

          # Remove one copy of base cost, and move list of costs to right stack
          {}(<>)<>{({}<>)<>}{}

          # If swap at end of substring, remove second-last match
          {(<{}>)<>{({}<>)<>}<>({}<{}>){({}<>)<>}}{}

          # Put end of substring on third stack
          ((({}<({}({})({})<

            # If swap at beginning of substring, remove first match
            {{}<>{}(<>)}{}

            # Move start of substring to other stack for safekeeping
            (((({}<({}<>)>)<>)))

          # Create "deletion" record, excluding cost
          <>>)<>>)<>

          # Move data to left stack
          <({}<({}<<>

            # Add cost to deletion record
            (()())

          >)>)>)

          # Put start position on third stack under end position
          <<>({}<

            # For each matching bracket cost:
            {}{

              # Move cost to left stack
              ({}<>)

              # Make three configurations
              ([()()()]){

                # Increment counter
                ((({}()()<>))[()]<

                  # Increment cost in first and third configurations
                  (({()(<{}>)}{})<>({}<

                    # Keep last position constant
                    (({}<

                      # Beginning of second interval: 1, 2, 1 past end of first
                      <>({}[()()]

                        # End of first interval: -3, -1, 1 plus current position
                        (()[({})({})]

                          # Move current position in first and third configurations
                          ({[()](<{}>)}{}<>{}<

                            (({})<>)

                          >)

                        <>)

                      )

                    >)<>)

                  >)<>)

                  # Move data back to left stack
                  <>({}<({}<({}<({}<>)>)>)>)

                >)

              }{}

            {}<>}

            # Eliminate last entry
            # NOTE: This could remove the deletion record if no possible matches.  This is no loss (probably).
            <>{}{}{}{}{}{}{}{}

        # Restore loop variables
        >)>)>)

      }{}

      # With current endpoints on third stack:
      ({}<({}<

        # For all entries
        {

          # Compute locations and move to right stack
          ({}<(({}){({}())}{}{}<(({}){({}())}{}{}<>)>)>)<>

        }

        # For all entries (now on right stack):
        <>{

          # Cost of match
          ((({}

            # Do twice:
            (()()){([{}](

              # Add cost of resulting substrings
              <({}(<()>)<>){({}<({}<>)>(())<>)}{}>({})<<>{{}({}<>)<>}{}>

            # Evaluate as sum of two runs
            ))([{}()]{})}{}

          )))

          # Find smaller of cost and current minimum
          <>(({}))<>{<>({}[()])}{}

          # Push new minimum in place of old minimum
          ({}<<>{}{}{<>}>)

          <>{}

        }

        # Subtract 1 if nonzero
        <>(({}<>){[()](<{}>)}{})(<>)

      >)>)

      <>(<({}<>)>)<>

    # Otherwise (length 3 or less), use 1 from earlier as cost.
    # Note that length 0-1 is impossible here.
    }<>{}

    # With cost on third stack:
    ({}<

      # Find slot number to store cost of interval
      (({}){({}())}{}{})

      # Move to slot
      {({}<({}<>)>(())<>)}{}

    # Store new cost
    {}>)

    # Move other slots back where they should be
    <>{{}({}<>)<>}{}

  Restore length/position for next iteration
  >)<>>)}

  # Clear length/position from inner loop
  {}<>([[]{}])

}{}

(([]){<{}{}>([])}{}<>){({}[()]<{}>)}{}({}<>)

2

하스켈 , 797 바이트

import Data.Array;import Data.Function;import Data.List;
e=length;f=fst;o=map;s=listArray;u=minimum;b p=let{m=e p;x=s(1,m)p;
v=s(1,m)(listArray('(','}')[0,0..]:[v!i//[(x!i,i)]|i<-[1..m-1]]);
d q=let{n=e q;y=s(1,n)q;t(a,b)=listArray((a,b),(m,n));
c=t(1,1)[sum[1|x!i/=y!j]|i<-[1..m],j<-[1..n]];
d=t(-1,-1)[if i<0||j<0then m+n else 
if i*j<1then(i+j)else u[1+d!(i-1,j),1+d!(i,j-1),c!(i,j)+d!(i-1,j-1),
let{k=v!i!(y!j)-1;l=w!(i,j-1)-1}in-3+i+j-k-l+d!(k,l)]|i<-[-1..m],j<-[-1..n]];
w=t(1,0)[if j>0&&c!(i,j)>0then w!(i,j-1)else j|i<-[1..m],j<-[0..n]]}in d!(m,n);
a=s(0,div m 2)([(m,"")]:[(concat.take 2.groupBy(on(==)f).sort.o(\q->(d q,q)))(
[b:c++[d]|[b,d]<-words"() <> [] {}",(_,c)<-a!(l-1)]++
concat[[b++d,d++b]|k<-[1..div l 2],(_,b)<-a!k,(_,d)<-a!(l-k)])|l<-[1..div m 2]]);
}in u(o(f.head)(elems a))

온라인으로 사용해보십시오!


어제 나는 현상금이 내일 전에 끝나지 않을 것이라고 읽었으므로 en.wikipedia.org/wiki/… 알고리즘을 적용하는 구현 이 현재의 훨씬 더 빠른 Retina 휴리스틱보다 더 정확한 값을 계산 한다는 것에 이의를 제기하고 싶었습니다 !
로마 Czyborra

아니, 이것은 2400s @ 800MHz에서 4의 먼 18자를 찡그린 것도 슬프게도 3600에 가까운 9의 먼 22자를 ok을 것이라고 잘못 외삽했기 때문에 결국 상을받을 가치가 없습니다.
로마 Czyborra
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.