平衡支架


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解决了一半的测试用例)

这是,最少字节获胜!

您的提交应该是可测试的,这意味着它应该在不超过一个小时的时间内输出每个测试用例的结果。


9
平衡括号:P
Christopher

3
如果我们能看到一个正确的非暴力解决方案,我将感到惊讶。
Orlp

5
@SIGSEGV这个问题的答案是1,这不要紧,你是否平衡它像[<>][]<><>
弥敦道美林

3
@Bijan Nah,这不会变得容易得多,此外,Brain-Flak的生日快到了!
帕维尔

3
@qwr为什么有限制?时间限制仅适用于给定的测试用例,对于大量输入,您的程序可能会花费世界上所有时间。
帕维尔

Answers:


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

在线尝试!

非暴力解决方案!它适用于所有测试用例,甚至发现一个错误。

-2个字节感谢@MartinEnder(${4}to $+

+12个字节用于说明其他交换情况

通过更好地使用字符类来获得-16个字节

消除不必要的交换限制,可减少-8个字节。这也修复了一个错误:)

通过将交换逻辑合并到单个正则表达式中来获得-10个字节

+2个字节用于说明连续的交换

+许多错误修复**

说明:

T`[]()`:;'"为方便起见,用于替换特殊的支架类型。首先,我们将所有匹配的括号递归替换为-AB或者69取决于它们是否相邻。

然后,通过删除新匹配的括号并1在字符串的开头添加a 来执行有用的“交换” 。我们还将-空字符串替换为空字符串,因为它只是用于上述交换。

接下来,我们尝试通过删除不与已匹配的括号重叠的不匹配的括号对并1在字符串中添加a,来进行“替换” 。

最后,\W|6B|1计算所有剩余的单括号加上1s 的数量。

**我目前正在开发使用Retina的行拆分功能的较短版本,尽管我遇到了一个相当大的问题,所以可能要花一些时间。


${4}可缩短至$+。我很好奇为什么可以保证这一点。
Martin Ender

@MartinEnder谢谢!我不确定它是否总能正常工作,但至少在所提供的测试用例以及我想到的一些边缘情况下都有效
math junkie

2
给定[{][}] [] [[][][][][][]] [][][][][][][][][][][][],您只需}在第一对括号内交换并使其平衡即可。间距用于使输入更具可读性。您输出了3,但实际上应该是1。
Anthony Pham

@AnthonyPham谢谢!我想我知道为什么这行不通,我将尝试寻找一种巧妙的方法来解决它
数学迷

甚至离奇的是,[{]}返回1,但[{][]}回报2
安东尼范

12

Brain-Flak,1350字节

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

在线尝试!

通过恒速比较和指针解引用,该算法为O(n 3)。不幸的是,Brain-Flak都不具备这些功能,因此该程序运行时间为O(n 5)。最长的测试用例大约需要15分钟。

简化结果

要查看我的算法是否有效,我们需要显示一些可大大减少搜索空间的结果。这些结果基于以下事实:目标是一种完整的语言,而不只是一种特定的字符串。

  • 无需插入。相反,您可以删除插入的字符最终会匹配的括号。

  • 您将不再需要删除括号,然后交换其两个邻居。看到这一点,假设wlog被移除的支架(,所以我们正在改变a(c,以ca在两个步骤。通过更改c和插入副本,我们可以ca()完成两个步骤而无需交换。(然后可以通过上述规则删除此插入。)

  • 相同的支架将不需要交换两次。通常,这是关于Damerau-Levenshtein距离的标准事实。

我没有使用过的另一个简化结果,因为考虑它们会花费字节:

  • 如果两个方括号互换,并且彼此不匹配,则与每个方括号的最终匹配将永远不会更改或交换。

算法

当任何字符串简化为平衡字符串时,下列条件之一将成立:

  • 第一个括号被删除。
  • 第一个括号停留在原处,并在某个位置与括号匹配k(可能在更改了一个或两个括号之后)。
  • 第一个支架与第二个支架互换,第二个支架又与位置处的支架匹配k

在第二种情况下,该位置的括号k可能已与其相邻的一个交换。在后两种情况中的任何一种情况下,(可能是新的)第一个方括号与就位开始的方括号之间的字符串都k必须编辑为平衡字符串,由之后的所有字符串组成的字符串也必须编辑k

这意味着可以使用动态编程方法。由于交换的括号不需要再次交换,因此我们只需要考虑连续的子字符串,以及通过从该子字符串中删除第二个字符和/或倒数第二个字符而形成的子序列。因此,我们只需要查看O(n 2)个子序列。其中每个都有O(n)种可能的方式来匹配(或删除)第一个括号,因此在上述条件下,算法为O(n 3)。

数据结构

右堆栈包括原始字符串中的括号,每个括号中有两个字节。第一个条目确定整个括号,并选择匹配的括号之间的差为正1。第二个条目仅确定它是一个开括号还是一个结束括号:这确定了两个括号匹配需要进行多少次更改彼此。没有显式的低于此的隐式零,因此我们可以[]用来获取此字符串的总长度。

所考虑的每个子字符串都由两个数字表示,范围在0到2n之间:一个代表开始位置,一个代表结束位置。解释如下:

  • 从开始的子字符串将从2k位置k(索引0)开始,并且不删除第二个字符。
  • 从开始的子字符串将从2k+1position开始k,并且第二个字符由于已向左交换而被删除。
  • 以结尾的子字符串2k将在位置之前结束k(即,范围是左包含和右包含)。
  • 以结尾的子字符串2k-1将在position之前结束k,倒数第二个字符由于已被正确交换而被删除。

某些范围内(kk+12k+12k+12k+12k+32k+12k+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启发式算法计算出更正确的值表示质疑!
Roman Czyborra

不,毕竟这是不值得的,因为我错误地推断出,它在2400s @ 800MHz处欺骗18个距离遥远的4个字符也会使22个字符远距离9同样接近3600s,这可悲的是。
Roman Czyborra
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.