ブラケットのバランスをとる


24

目的:ブラケットの文字列を指定し、入力文字列をブラケットのバランスが取れた文字列に変換するために必要な最小のダメラウ-レーベンシュタイン距離を出力します。

入力

入力文字列には括弧のみが含まれ、他の文字は含まれません。つまり、の任意の文字の組み合わせです(){}[]<>。入力は、文字列または文字の配列として取得できます。入力文字列について他の仮定を行うことはできません。任意の長さ(言語でサポートされている最大サイズまで)、空の場合、ブラケットのバランスがすでに取れている場合などがあります。

ダメラウ-レーベンシュタイン距離

2つの文字列間のDamerau-Levenshtein Distanceは、2つの隣接する文字の挿入、削除、単一文字の置換、および転置(スワップ)の最小数です。

出力

出力は、入力文字列と角かっこが一致する文字列との間の最小Damerau-Levenshtein Distanceである必要があります。出力は、結果のバランスの取れた文字列ではなく、数値でなければなりません。

次のように、開始ブラケットと終了ブラケットが正しい順序であり、中に文字がない場合、一対のブラケットは「一致」とみなされます。

()
[]{}

または、その中のすべてのサブ要素も一致する場合。

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

サブ要素は、いくつかのレイヤーの深さにネストすることもできます。

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

(定義については@DJMcMayhemに感謝します)

テストケース

Input                   Possible Balanced       Output

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

(テストケースの半分を解決してくれた@WheatWizardに感謝)

これは、最少バイトが勝ちます!

提出物はテスト可能でなければなりません。つまり、テストケースごとに1時間以内に結果を出力する必要があります。


9
自分のブラケットのバランスをとる:P
クリストファー

3
このチャレンジに対して、非武装勢力の正解が1つだけ表示されたとしても驚かされます。
orlp

5
@SIGSEGVその答えは、それはあなたがそれのようにバランスを取るかどうかは関係ありません1である[<>]か、[]<>または<>
ネイサンメリル

3
@Bijan Nah、それはそれほど簡単ではありません、さらに、Brain-Flakの誕生日はすぐに来ています!
パベル

3
@qwr制限があるのはなぜですか?時間制限は、指定されたテストケースにのみ適用されます。大規模な入力の場合、プログラムは世界中で常に時間がかかる可能性があります。
パベル

回答:


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

オンラインでお試しください!

非強引なソリューション!すべてのテストケースで機能し、1つのエラーも検出しました。

@MartinEnder(${4}to $+)のおかげで-2バイト

追加のスワッピングケースを考慮して+12バイト

文字クラスをより有効に活用して-16バイト

スワップの不要な制限を削除することにより、-8バイト。これもバグを修正しました:)

スワッピングロジックを単一の正規表現に結合することにより、-10バイト

連続スワップに対応するための+2バイト

+多くのさまざまなバグ修正**

説明:

T`[]()`:;'"便宜上、特殊なブラケットタイプを置き換えるために使用されます。まず、一致したすべてのブラケットを再帰的に-ABまたは69隣接するかどうかに応じて置き換えます。

次に、新しく一致した角かっこを削除し1、文字列の先頭にa を追加することにより、有用な「スワップ」が実行されます。また-、上記のスワッピングに使用されていたため、空の文字列に置き換えます。

次に、すでに一致する括弧と重ならない一致しない括弧のペアを削除1し、文字列にa を追加することにより、「置換」を試みます。

最後に、\W|6B|1残りの単一角かっこと1s の数をカウントします。

**現在、Retinaの行分割機能を使用する短いバージョンに取り組んでいますが、かなりの問題が発生したため、かなり時間がかかる可能性があります。


これ${4}はに短縮できます$+。なぜこれが機能することが保証されているのか、とても興味があります。
マーティンエンダー

@MartinEnderありがとう!私はそれがわからないんだけど、常に動作しますが、それは提供されるテストケースのために、少なくとも動作し、カップルのエッジケースは私が思いついたことを
中毒数学

2
与えられた場合[{][}] [] [[][][][][][]] [][][][][][][][][][][][]}括弧の最初のペアの内側を単純に交換し、バランスをとることができます。入力を少し読みやすくするために、間隔が使用されます。3を出力しましたが、実際には1つである必要があります。
アンソニーファム

@AnthonyPhamありがとう!私はそれがうまくいかない理由を知っていると思う、私はそれを修正する賢い方法を見つけようとする
数学ジャンキー

でも奇妙ことである[{]}1を返しますが[{][]}戻って2
アンソニー・ファム

12

Brain-Flak、1350バイト

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

オンラインでお試しください!

一定速度の比較とポインターの逆参照では、このアルゴリズムはO(n 3)です。残念ながら、Brain-Flakにはこれらのいずれも含まれていないため、このプログラムは代わりにO(n 5)時間で実行されます。最も長いテストケースには約15分かかります。

結果の簡素化

私のアルゴリズムが機能することを確認するには、検索スペースを大幅に削減する結果を表示する必要があります。これらの結果は、ターゲットが1つの特定の文字列ではなく、言語全体であるという事実に依存しています。

  • 挿入は必要ありません。代わりに、挿入された文字が最終的に一致するブラケットを削除することができます。

  • ブラケットを削除する必要はありません。その後、2つの隣のブラケットを交換します。これを見るために、削除されたブラケットがであるとwlogを想定して、2つのステップでに(変換a(ccaています。cコピーを変更して挿入することによりca()、スワップなしで2つのステップで到達できます。(この挿入は、上記のルールによって削除できます。)

  • 同じブラケットを2回交換する必要はありません。これは、一般的にダメラウ-レーベンシュタイン距離に関する標準的な事実です。

私が使用しなかった別の単純化の結果は、それらを考慮するとバイトがかかるためです:

  • 2つのブラケットが交換され、それらが互いに一致しない場合、それらのブラケットのそれぞれに対する最終的な一致は変更または交換されません。

アルゴリズム

文字列がバランスの取れた文字列に縮小されると、次のいずれかが当てはまります。

  • 最初のブラケットが削除されます。
  • 最初のブラケットは現在の位置にとどまり、ある位置でブラケットと一致しますk(おそらくその一方または両方を変更した後)。
  • 最初のブラケットは2番目のブラケットと交換され、位置2のブラケットと一致しkます。

2番目のケースでは、位置のブラケットkが隣のブラケットと交換されている可能性があります。後者の2つの場合のいずれにおいても、(おそらく新たに)最初のブラケットと位置で始まるブラケットの間の文字列は、k以降のすべてで構成される文字列と同様に、バランスの取れた文字列に編集する必要がありますk

これは、動的プログラミングアプローチを使用できることを意味します。交換されたブラケットを再度交換する必要はないため、連続する部分文字列と、そのような部分文字列から2番目の文字や最後から2番目の文字を削除することによって形成される部分列のみを考慮する必要があります。したがって、調べる必要があるのはO(n 2)サブシーケンスのみです。これらのそれぞれには、最初のブラケットに一致(または削除)するO(n)可能な方法があるため、上記の条件下ではアルゴリズムはO(n 3)になります。

データ構造

右側のスタックには、元の文字列のブラケットが含まれ、ブラケットごとに2バイトが含まれます。最初のエントリはブラケット全体を決定し、一致するブラケットの差が正確に1になるように選択されます。2番目のエントリは、開始ブラケットか終了ブラケットかを決定するだけです。お互い。[]この文字列の全長を取得するために使用できるように、これより下の暗黙的なゼロは明示的になりません。

検討中の各部分文字列は、0〜2nの範囲の2つの数値で表されます。1つは開始位置、もう1つは終了位置です。解釈は次のとおりです。

  • で始まる部分文字列2kは位置k(0 から始まる)から始まり、2番目の文字は削除されません。
  • で始まる部分文字列2k+1は位置kから始まり、2番目の文字は左にスワップされたため削除されます。
  • で終わる部分文字列は、2k位置の直前で終了しますk(つまり、範囲は左と右を含みます)。
  • で終わる部分文字列は2k-1positionの直前で終了kし、最後から2番目の文字は右にスワップされたため削除されます。

一部の範囲(kto k+12k+1to 2k+12k+1to 2k+3、および2k+1to 2k+5)は、物理的な意味を持ちません。とにかく中間値として表示されるものもあります。これは、チェックを追加して回避するよりも簡単だからです。

左側のスタックには、各部分文字列をバランスの取れた文字列に変換するために必要な編集の数が格納されます。間隔の編集距離(x,y)はdepthに保存されx + y(y-1)/2ます。

内側のループ中に、左のスタックの上にエントリが追加され、どの移動が可能かが示されます。これらのエントリの長さは5バイトです。トップから数えては、番号はd+1y1x1y2x2、どこ動きがコスト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

Haskell、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ヒューリスティックよりも正しい値を計算することを争いたいと思います。
ローマンチボラ

いいえ、これは結局のところ賞に値しません。なぜなら、2400s @ 800MHzで18文字離れた4文字を調べると、3600に近い22文字離れた9文字も同じように間違って外挿したからです。
ローマンチボラ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.