Balance the Brackets

24

Seu objetivo: Dada uma sequência de colchetes, produza a distância mínima de Damerau-Levenshtein necessária para transformar a sequência de entrada em uma sequência em que os colchetes estejam balanceados.

Entrada

A sequência de entrada conterá apenas colchetes e nenhum outro caractere. Ou seja, é uma combinação de qualquer um dos caracteres (){}[]<>. Você pode receber a entrada como uma sequência ou uma matriz de caracteres. Você não pode fazer outras suposições sobre a sequência de entrada; pode ser arbitrariamente longo (até o tamanho máximo suportado pelo seu idioma), pode estar vazio, os colchetes já devem estar balanceados etc.

Distância Damerau-Levenshtein

A distância Damerau-Levenshtein entre duas cadeias é o número mínimo de inserções, exclusões, substituições de caracteres únicos e transposições (troca) de dois caracteres adjacentes.

Saída

A saída deve ser a distância mínima de Damerau-Levenshtein entre a sequência de entrada e uma sequência na qual os colchetes são correspondentes. A saída deve ser um número , não a sequência balanceada resultante.

Um par de colchetes é considerado "correspondente" se os colchetes de abertura e fechamento estiverem na ordem correta e não tiverem caracteres dentro deles, como

()
[]{}

Ou se todos os subelementos dentro dele também corresponderem.

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

Os subelementos também podem ser aninhados em várias camadas de profundidade.

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

(Obrigado a @DJMcMayhem pela definição)

Casos de teste

Input                   Possible Balanced       Output

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

(Obrigado ao @WheatWizard por resolver metade dos casos de teste)

Isso é , o menor número de bytes vence!

Seus envios devem ser testáveis, ou seja, devem gerar um resultado para cada caso de teste em não mais de uma hora.

Pavel
fonte
9
Equilibre seus próprios colchetes: P
Christopher
3
Ficarei surpreso se vermos uma única resposta correta, não relacionada à força bruta, para esse desafio.
orlp
5
@SIGSEGV, a resposta para isso é 1. Não importa se você equilibra isso como [<>]ou []<>ou<>
Nathan Merrill
3
@Bijan Nah, não será muito mais fácil e, além disso, o aniversário da Brain-Flak está chegando!
Pavel
3
@qwr Por que ter um limite? O limite de tempo se aplica apenas aos casos de teste fornecidos. Para grandes entradas, seu programa pode demorar o tempo todo no mundo.
Pavel

Respostas:

13

Retina, 254 252 264 248 240 232 267 bytes

Obrigado a @AnthonyPham, @officialaimm e @MistahFiggins por apontar bugs

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

Experimente Online!

Solução de força não bruta! Ele funciona para todos os casos de teste e até encontrou um erro em um.

-2 bytes graças a @MartinEnder ( ${4}to $+)

+12 bytes para contabilizar casos de troca adicionais

-16 bytes, fazendo um melhor uso das classes de caracteres

-8 bytes removendo uma restrição desnecessária na troca. Isso também corrigiu um bug :)

-10 bytes combinando a lógica de troca em um único regex

+2 bytes para contabilizar swaps consecutivos

+ muitos para várias correções de bugs **

Explicação:

T`[]()`:;'"é usado para substituir tipos de suporte especiais por conveniência. Primeiro, substituímos recursivamente todos os colchetes correspondentes por -, ABou69 dependendo de serem adjacentes ou não.

Em seguida, a "troca" útil é realizada removendo os colchetes recém-correspondidos e adicionando um 1ao início da string. Também substituímos -pela string vazia, pois ela estava sendo usada apenas para a troca acima.

Em seguida, tentamos "substituições" removendo pares de colchetes não correspondentes que não se sobrepõem aos colchetes já correspondentes e adicionando um 1à string.

Finalmente, \W|6B|1conta todos os colchetes restantes restantes mais o número de 1s.

** Atualmente, estou trabalhando em uma versão mais curta que usa os recursos de divisão de linha da Retina, embora tenha tido um problema considerável, por isso pode demorar um pouco.

viciado em matemática
fonte
Isso ${4}pode ser reduzido para $+. Estou muito curioso por que isso é garantido que funcione.
Martin Ender
@MartinEnder Thanks! Eu não tenho certeza que ele sempre funciona, mas funciona, pelo menos para os casos de teste fornecidos, e um casos de ponta casal que eu vim com
matemática viciado em
2
Dado [{][}] [] [[][][][][][]] [][][][][][][][][][][][], você pode simplesmente trocar o }interior do primeiro par de parênteses e equilibrá-lo. O espaçamento é usado para tornar a entrada um pouco mais legível. Você produziu 3, mas realmente deveria ser um.
Anthony Pham
@AnthonyPham Thanks! Eu acho que sei por que não está funcionando, eu vou tentar encontrar uma maneira inteligente de corrigi-lo
viciado em matemática
Ainda mais estranho é que [{]}retorna 1, mas [{][]}retorna 2.
Anthony Pham
12

Flak cerebral , 1350 bytes

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

Experimente online!

Com comparações de velocidade constante e desreferenciamento de ponteiro, esse algoritmo é O (n 3 ). Infelizmente, o Brain-Flak não possui nenhum desses, portanto, esse programa é executado no tempo O (n 5 ). O caso de teste mais longo leva cerca de 15 minutos.

Simplificando resultados

Para ver que meu algoritmo funciona, precisamos mostrar alguns resultados que reduzam consideravelmente o espaço de pesquisa. Esses resultados se baseiam no fato de que o destino é um idioma inteiro, em vez de apenas uma sequência específica.

  • Não são necessárias inserções. Em vez disso, você pode apenas remover o suporte que o caractere inserido acabaria por corresponder.

  • Você nunca precisará remover um suporte e trocar os dois vizinhos. Para ver isso, assumir WLOG que o suporte removido é (, por isso, estamos transformando a(ca caem duas etapas. Ao alterar ce inserir uma cópia, podemos chegar ca()em duas etapas sem troca. (Essa inserção pode ser removida pela regra acima.)

  • O mesmo suporte nunca precisará ser trocado duas vezes. Este é um fato padrão sobre a distância Damerau-Levenshtein em geral.

Outro resultado simplificador que eu não usei, porque contabilizá-los custaria bytes:

  • Se dois colchetes forem trocados e não coincidirem, a eventual correspondência com cada um desses colchetes nunca será alterada ou trocada.

O algoritmo

Quando qualquer string é reduzida a uma string balanceada, uma das seguintes opções será verdadeira:

  • O primeiro colchete é excluído.
  • O primeiro suporte permanece onde está e corresponde ao suporte em alguma posição k(possivelmente após a alteração de um ou de ambos).
  • O primeiro suporte é trocado pelo segundo, que, por sua vez, corresponde ao suporte na posição k.

No segundo caso, o suporte na posição kpode ter sido trocado por um de seus vizinhos. Nos dois últimos casos, a string entre o (possivelmente novo) primeiro colchete e o colchete iniciado na posição kdeve ser editada em uma string balanceada, assim como a string que consiste em tudo o que está depois k.

Isso significa que uma abordagem de programação dinâmica pode ser usada. Como um colchete trocado não precisa ser trocado novamente, precisamos considerar apenas substrings contíguos, bem como subsequências formadas pela remoção do segundo caractere e / ou penúltimo caractere de tal substring. Portanto, existem apenas O (n 2 ) subsequências que precisamos examinar. Cada uma delas possui O (n) maneiras possíveis de corresponder (ou excluir) o primeiro colchete; portanto, o algoritmo seria O (n 3 ) nas condições acima.

A estrutura de dados

A pilha direita inclui os colchetes da sequência original, com dois bytes por colchete. A primeira entrada determina o colchete inteiro e é escolhida de forma que os colchetes correspondentes tenham uma diferença de exatamente 1. A segunda entrada determina apenas se é um colchete de abertura ou um colchete de fechamento: isso determina quantas alterações são necessárias para que dois colchetes correspondam entre si. Nenhum zeros implícito abaixo disso é explicitado, para que possamos usar[] para obter o comprimento total dessa string.

Cada substring em consideração é representado por dois números no intervalo de 0 a 2n: um para a posição inicial e outro para o final. A interpretação é a seguinte:

  • Uma substring iniciando em 2kcomeçará na posição k(indexada 0) e o segundo caractere não será removido.
  • Uma subcadeia que inicia em 2k+1inicia na posição ke o segundo caractere é removido por ter sido trocado para a esquerda.
  • Uma substring que termina em 2ktermina antes da posição k(ou seja, o intervalo é inclusivo à esquerda e exclusivo à direita).
  • Uma substring que termina em 2k-1termina antes da posição ke o penúltimo caractere é removido por ter sido trocado para a direita.

Alguns intervalos ( kpara k+1, 2k+1para 2k+1, 2k+1para 2k+3e 2k+1para 2k+5) não fazem sentido físico. De qualquer forma, alguns deles aparecem como valores intermediários, porque é mais fácil do que adicionar verificações adicionais para evitá-los.

A pilha esquerda armazena o número de edições necessárias para converter cada substring em uma sequência equilibrada. A distância de edição do intervalo (x,y)é armazenada em profundidade x + y(y-1)/2.

Durante o loop interno, as entradas são adicionadas acima da pilha esquerda para indicar quais movimentos são possíveis. Essas entradas têm 5 bytes. Contando a partir do topo, os números são d+1, y1, x1, y2, x2, onde o movimento custa deditar etapas e divide o substring em (x1,y1)e (x2,y2).

O código

Descrição para vir. Por enquanto, aqui está minha cópia de trabalho do código. Alguns comentários podem ser inconsistentes com a terminologia.

# 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
  {}<>([[]{}])

}{}

(([]){<{}{}>([])}{}<>){({}[()]<{}>)}{}({}<>)
Nitrodon
fonte
2

Haskell , 797 bytes

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))

Experimente online!

Roman Czyborra
fonte
Ontem eu li aqui que a recompensa não terminaria antes de amanhã, portanto, eu queria contestar que uma implementação aplicando o algoritmo en.wikipedia.org/wiki/… calcule valores mais corretos do que a atual heurística da retina muito mais rápida!
Roman Czyborra
Não, isso não vale o prêmio, afinal de contas, porque extrapolou erroneamente que seu grokking os 18 caracteres distantes 4 em 2400s @ 800MHz também grok os 22 caracteres distantes 9 igualmente próximos dos 3600s, o que infelizmente não seria.
Roman Czyborra