Bilancia le staffe


24

Il tuo obiettivo: data una stringa di parentesi, genera la distanza minima di Damerau-Levenshtein richiesta per trasformare la stringa di input in una stringa in cui le parentesi sono bilanciate.

Ingresso

La stringa di input conterrà solo parentesi e nessun altro carattere. Cioè, è una combinazione di uno qualsiasi dei personaggi in (){}[]<>. È possibile accettare input come una stringa o una matrice di caratteri. Non è possibile fare altre ipotesi sulla stringa di input; potrebbe essere arbitrariamente lungo (fino alla dimensione massima supportata dalla tua lingua), potrebbe essere vuoto, le parentesi potrebbero essere già bilanciate, ecc.

Distanza Damerau-Levenshtein

La distanza Damerau-Levenshtein tra due stringhe è il numero minimo di inserzioni, eliminazioni, sostituzioni di caratteri singoli e trasposizioni (scambio) di due caratteri adiacenti.

Produzione

L'output dovrebbe essere la distanza minima di Damerau-Levenshtein tra la stringa di input e una stringa in cui le parentesi sono abbinate. L'output dovrebbe essere un numero , non la stringa bilanciata risultante.

Una coppia di parentesi è considerata "abbinata" se le parentesi aperta e chiusa sono nell'ordine giusto e non hanno caratteri al loro interno, come

()
[]{}

O se anche ogni sottoelemento al suo interno è abbinato.

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

Gli elementi secondari possono anche essere nidificati a più livelli di profondità.

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

(Grazie a @DJMcMayhem per la definizione)

Casi test

Input                   Possible Balanced       Output

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

(Grazie a @WheatWizard per aver risolto metà dei casi di test)

Questo è , vince meno byte!

I tuoi invii dovrebbero essere testabili, il che significa che dovrebbe generare un risultato per ogni caso di test in non più di un'ora.


9
Bilancia le tue parentesi: P
Christopher

3
Sarò sorpreso se vedremo anche un'unica risposta corretta e non bruta a questa sfida.
orlp,

5
@SIGSEGV la risposta a questa è 1. Non importa se si bilancia piace [<>]o []<>o<>
Nathan Merrill

3
@Bijan Nah, non renderà molto più facile, e inoltre, il compleanno di Brain-Flak sta arrivando presto!
Pavel,

3
@qwr Perché avere un limite? Il limite di tempo si applica solo ai casi di test indicati, per input di grandi dimensioni il tuo programma può impiegare tutto il tempo nel mondo.
Pavel

Risposte:


13

Retina, 254 252 264 248 240 232 267 byte

Grazie a @AnthonyPham, @officialaimm e @MistahFiggins per aver segnalato i bug

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

Provalo online!

Soluzione di forza non bruta! Funziona per tutti i casi di test e ha persino riscontrato un errore in uno.

-2 byte grazie a @MartinEnder ( ${4}a $+)

+12 byte per tenere conto di ulteriori casi di scambio

-16 byte sfruttando meglio le classi di caratteri

-8 byte rimuovendo una restrizione non necessaria sullo scambio. Anche questo ha corretto un bug :)

-10 byte combinando la logica di scambio in una singola regex

+2 byte per tenere conto degli swap consecutivi

+ molti per varie correzioni di bug **

Spiegazione:

T`[]()`:;'"viene utilizzato per sostituire tipi di staffe speciali per comodità. Innanzitutto, sostituiamo in modo ricorsivo tutte le parentesi abbinate con -, ABo a 69seconda che siano adiacenti o meno.

Quindi, l'utile "scambio" viene eseguito rimuovendo le parentesi appena abbinate e aggiungendo 1a all'inizio della stringa. Sostituiamo anche -con la stringa vuota, poiché era appena stata utilizzata per lo scambio di cui sopra.

Quindi, proviamo i "rimpiazzi" rimuovendo le coppie di parentesi senza pari che non si sovrappongono alle parentesi già abbinate e aggiungendo un 1alla stringa.

Infine, \W|6B|1conta tutte le parentesi singole rimanenti più il numero di 1s.

** Attualmente sto lavorando a una versione più breve che utilizza le funzionalità di suddivisione della linea di Retina, anche se ho riscontrato un problema considerevole, quindi potrebbe richiedere parecchio tempo.


Questo ${4}può essere abbreviato $+. Sono molto curioso di sapere perché questo è garantito per funzionare.
Martin Ender,

@MartinEnder Grazie! Non sono sicuro che funzioni sempre , ma funziona almeno per i casi di test forniti e un paio di casi limite che mi sono venuti in mente
drogato di matematica

2
Dato [{][}] [] [[][][][][][]] [][][][][][][][][][][][], puoi semplicemente scambiare l' }interno della prima coppia di parentesi e averlo bilanciato. La spaziatura viene utilizzata per rendere l'input un po 'più leggibile. Hai prodotto 3 ma dovrebbe essere davvero uno.
Anthony Pham,

@AnthonyPham Grazie! Penso di sapere perché non funziona, cercherò di trovare un modo intelligente per risolverlo
drogato di matematica

Ancora più strano è che [{]}restituisce 1 ma [{][]}restituisce 2.
Anthony Pham

12

Brain-Flak , 1350 byte

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

Provalo online!

Con confronti a velocità costante e dereferenziazione del puntatore, questo algoritmo è O (n 3 ). Sfortunatamente, Brain-Flak non ha nessuno di questi, quindi questo programma funziona invece in O (n 5 ). Il test più lungo dura circa 15 minuti.

Semplificazione dei risultati

Per vedere che il mio algoritmo funziona, dobbiamo mostrare alcuni risultati che riducono considerevolmente lo spazio di ricerca. Questi risultati si basano sul fatto che l'obiettivo è un'intera lingua anziché solo una stringa specifica.

  • Non sono necessari inserimenti. Invece, puoi semplicemente rimuovere la parentesi che il carattere inserito alla fine corrisponderebbe.

  • Non dovrai mai rimuovere una parentesi, quindi scambiare i suoi due vicini. Per vedere questo, si supponga che la staffa WLOG rimosso è (, quindi stiamo trasformando a(ca cadue passi. Modificando ce inserendo una copia, possiamo raggiungere ca()in due passaggi senza uno scambio. (Questo inserimento può quindi essere rimosso dalla regola sopra.)

  • La stessa parentesi non dovrà mai essere scambiata due volte. Questo è un fatto standard sulla distanza Damerau-Levenshtein in generale.

Un altro risultato di semplificazione che non ho usato, poiché la loro contabilità costerebbe byte:

  • Se due parentesi vengono scambiate e non coincidono, l'eventuale corrispondenza con ciascuna di queste parentesi non verrà mai modificata o scambiata.

L'algoritmo

Quando una stringa viene ridotta a una stringa bilanciata, sarà vera una delle seguenti condizioni:

  • La prima parentesi viene eliminata.
  • La prima parentesi rimane dove si trova e corrisponde alla parentesi in una certa posizione k(possibilmente dopo aver cambiato una o entrambe).
  • La prima parentesi viene scambiata con la seconda, che a sua volta corrisponde alla staffa in posizione k.

Nel secondo caso, la staffa in posizione kpotrebbe essere stata scambiata con uno dei suoi vicini. In uno di questi ultimi due casi, la stringa tra la (eventualmente nuova) prima parentesi e la parentesi che è iniziata in posizione kdeve essere modificata in una stringa bilanciata, così come la stringa costituita da tutto ciò che segue k.

Ciò significa che può essere utilizzato un approccio di programmazione dinamica. Poiché non è necessario sostituire nuovamente una parentesi di scambio, è necessario considerare solo sottostringhe contigue, nonché sottosequenze formate rimuovendo il secondo carattere e / o il penultimo carattere da tale sottostringa. Quindi, ci sono solo le sottosequenze O (n 2 ) che dobbiamo guardare. Ognuno di questi ha O (n) possibili modi per abbinare (o eliminare) la prima parentesi, quindi l'algoritmo sarebbe O (n 3 ) nelle condizioni sopra.

La struttura dei dati

Lo stack corretto include le parentesi dalla stringa originale, con due byte per parentesi. La prima voce determina l'intera parentesi, ed è scelta in modo tale che le parentesi corrispondenti abbiano una differenza esattamente di 1. La seconda voce determina solo se si tratta di una parentesi aperta o di una parentesi chiusa: determina quante modifiche sono necessarie affinché due parentesi corrispondano l'un l'altro. Nessuno zero implicito al di sotto di questo viene mai reso esplicito, in modo che possiamo usare []per ottenere la lunghezza totale di questa stringa.

Ogni sottostringa in esame è rappresentata da due numeri nell'intervallo da 0 a 2n: uno per la posizione iniziale e uno per la fine. L'interpretazione è la seguente:

  • Una sottostringa che inizia da 2kinizierà in posizione k(indicizzata 0) e il secondo carattere non verrà rimosso.
  • Una sottostringa che inizia da 2k+1inizia in posizione ke il secondo carattere viene rimosso a causa dello scambio a sinistra.
  • Una sottostringa che termina in 2kterminerà appena prima della posizione k(ovvero, l'intervallo è compreso a sinistra ed esclusivo a destra).
  • Una sottostringa che termina in 2k-1termina appena prima della posizione ke il penultimo carattere viene rimosso a causa dello scambio corretto.

Alcuni intervalli ( kda k+1, 2k+1a 2k+1, 2k+1a 2k+3e 2k+1a 2k+5) non hanno alcun senso fisico. Alcuni di questi si presentano comunque come valori intermedi, perché è più semplice che aggiungere ulteriori controlli per evitarli.

Lo stack di sinistra memorizza il numero di modifiche necessarie per convertire ogni sottostringa in una stringa bilanciata. La distanza di modifica per l'intervallo (x,y)viene memorizzata in profondità x + y(y-1)/2.

Durante il ciclo interno, le voci vengono aggiunte sopra lo stack di sinistra per indicare quali mosse sono possibili. Queste voci sono lunghe 5 byte. Contando dalla cima, i numeri sono d+1, y1, x1, y2, x2, in cui la mossa costa dmodificare passi e divide la stringa in (x1,y1)e (x2,y2).

Il codice

Descrizione a venire. Per ora, ecco la mia copia di lavoro del codice. Alcuni commenti potrebbero essere incompatibili con la 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
  {}<>([[]{}])

}{}

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

2

Haskell , 797 byte

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

Provalo online!


Ieri ho letto qui che la grazia non sarebbe finita prima di domani, quindi volevo contestare che un'implementazione applicando l' algoritmo en.wikipedia.org/wiki/… calcola valori più corretti dell'attuale euristica Retina molto più veloce!
Roman Czyborra,

No, questo non è degno del premio dopo tutto perché ho erroneamente estrapolato il fatto che il suo grokking a 18 caratteri distanti 4 in 2400s a 800MHz avrebbe anche ingarbugliato i 22 caratteri distanti 9 ugualmente vicino a 3600s che purtroppo non avrebbe fatto.
Roman Czyborra,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.