Équilibrer les supports

24

Votre objectif: étant donné une chaîne de crochets, affichez la distance Damerau-Levenshtein minimale requise pour transformer la chaîne d'entrée en une chaîne où les crochets sont équilibrés.

Contribution

La chaîne d'entrée ne contiendra que des crochets et aucun autre caractère. Autrement dit, il s'agit d'une combinaison de l'un des caractères de (){}[]<>. Vous pouvez prendre l'entrée sous la forme d'une chaîne ou d'un tableau de caractères. Vous ne pouvez faire aucune autre hypothèse sur la chaîne d'entrée; il peut être arbitrairement long (jusqu'à la taille maximale prise en charge par votre langue), il peut être vide, les crochets peuvent déjà être équilibrés, etc.

Distance Damerau-Levenshtein

La distance Damerau-Levenshtein entre deux chaînes est le nombre minimum d'insertions, de suppressions, de substitutions à un seul caractère et de transpositions (permutation) de deux caractères adjacents.

Sortie

La sortie doit être la distance Damerau-Levenshtein minimale entre la chaîne d'entrée et une chaîne dans laquelle les parenthèses correspondent. La sortie doit être un nombre , pas la chaîne équilibrée résultante.

Une paire de crochets est considérée comme "assortie" si les crochets d'ouverture et de fermeture sont dans le bon ordre et ne contiennent aucun caractère, comme

()
[]{}

Ou si chaque sous-élément à l'intérieur de celui-ci est également mis en correspondance.

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

Les sous-éléments peuvent également être imbriqués plusieurs couches en profondeur.

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

(Merci à @DJMcMayhem pour la définition)

Cas de test

Input                   Possible Balanced       Output

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

(Merci à @WheatWizard d'avoir résolu la moitié des cas de test)

C'est du , le moins d'octets gagne!

Vos soumissions doivent pouvoir être testées, ce qui signifie qu'elles devraient produire un résultat pour chaque cas de test en moins d'une heure.

Pavel
la source
9
Équilibrez vos propres supports: P
Christopher
3
Je serai surpris si nous voyons même une seule réponse correcte non bruteforce à ce défi.
orlp
5
@SIGSEGV la réponse à cela est 1. Peu importe que vous équilibriez cela comme [<>]ou []<>ou<>
Nathan Merrill
3
@Bijan Nah, cela ne rendra pas les choses beaucoup plus faciles, et en plus, l'anniversaire de Brain-Flak arrive bientôt!
Pavel
3
@qwr Pourquoi avoir une limite? Le délai s'applique uniquement aux cas de test donnés, pour les entrées importantes, votre programme peut prendre tout le temps dans le monde.
Pavel

Réponses:

13

Rétine, 254 252 264 248 240 232 267 octets

Merci à @AnthonyPham, @officialaimm et @MistahFiggins pour avoir signalé les bogues

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

Essayez-le en ligne!

Solution de force non brute! Il fonctionne pour tous les cas de test et a même trouvé une erreur dans l'un.

-2 octets grâce à @MartinEnder ( ${4}à $+)

+12 octets pour tenir compte des cas d'échange supplémentaires

-16 octets en faisant un meilleur usage des classes de caractères

-8 octets en supprimant une restriction inutile sur l'échange. Cela a également corrigé un bug :)

-10 octets en combinant la logique de permutation en une seule expression régulière

+2 octets pour tenir compte des swaps consécutifs

+ beaucoup pour diverses corrections de bugs **

Explication:

T`[]()`:;'"est utilisé pour remplacer des types de supports spéciaux pour plus de commodité. Tout d'abord, nous remplaçons récursivement tous les supports correspondants par -, ABou 69selon qu'ils sont adjacents ou non.

Ensuite, un "échange" utile est effectué en supprimant les crochets nouvellement appariés et en ajoutant un 1au début de la chaîne. Nous remplaçons également -par la chaîne vide, car elle était juste utilisée pour l'échange ci-dessus.

Ensuite, nous essayons les «remplacements» en supprimant des paires de crochets sans correspondance qui ne chevauchent pas les crochets déjà appariés et en ajoutant un 1à la chaîne.

Enfin, \W|6B|1compte les parenthèses simples restantes plus le nombre de 1s.

** Je travaille actuellement sur une version plus courte qui utilise les fonctionnalités de partage de ligne de Retina, bien que j'ai rencontré un problème considérable, donc cela pourrait prendre un certain temps.

accro aux mathématiques
la source
Cela ${4}peut être raccourci $+. Je suis très curieux de savoir pourquoi cela est garanti de fonctionner.
Martin Ender
@MartinEnder Merci! Je ne suis pas sûr que cela fonctionne toujours , mais cela fonctionne au moins pour les cas de test fournis, et quelques cas de bord que j'ai trouvé
math junkie
2
Étant donné [{][}] [] [[][][][][][]] [][][][][][][][][][][][], vous pouvez simplement échanger l' }intérieur de la première paire de parenthèses et l'équilibrer. L'espacement est utilisé pour rendre l'entrée un peu plus lisible. Vous en avez sorti 3 mais ça devrait vraiment en être un.
Anthony Pham
@AnthonyPham Merci! Je pense que je sais pourquoi cela ne fonctionne pas, je vais essayer de trouver un moyen intelligent de le réparer
junkie mathématique
Encore plus étrange, cela [{]}renvoie 1 mais [{][]}renvoie 2.
Anthony Pham
12

Brain-Flak , 1350 octets

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

Essayez-le en ligne!

Avec des comparaisons à vitesse constante et un déréférencement de pointeurs, cet algorithme est O (n 3 ). Malheureusement, Brain-Flak n'a ni l'un ni l'autre, donc ce programme s'exécute en temps O (n 5 ) à la place. Le cas de test le plus long prend environ 15 minutes.

Simplifier les résultats

Pour voir que mon algorithme fonctionne, nous devons montrer quelques résultats qui réduisent considérablement l'espace de recherche. Ces résultats reposent sur le fait que la cible est une langue entière au lieu d'une seule chaîne spécifique.

  • Aucune insertion n'est nécessaire. Au lieu de cela, vous pouvez simplement supprimer le crochet auquel le caractère inséré correspondra éventuellement.

  • Vous n'aurez jamais besoin de retirer un support, puis d'échanger ses deux voisins. Pour voir cela, supposez wlog que le support supprimé est (, nous nous transformons a(cdonc caen deux étapes. En modifiant cet en insérant une copie, nous pouvons atteindre ca()en deux étapes sans échange. (Cette insertion peut ensuite être supprimée par la règle ci-dessus.)

  • Le même support ne devra jamais être échangé deux fois. C'est un fait standard sur la distance Damerau-Levenshtein en général.

Un autre résultat simplifiant que je n'ai pas utilisé, car leur comptabilisation coûterait des octets:

  • Si deux crochets sont échangés et qu'ils ne correspondent pas, la correspondance finale avec chacun de ces crochets ne sera jamais modifiée ou échangée.

L'algorithme

Lorsqu'une chaîne est réduite à une chaîne équilibrée, l'une des conditions suivantes est vraie:

  • Le premier crochet est supprimé.
  • Le premier support reste où il est et correspond au support à une certaine position k(éventuellement après avoir changé l'un ou les deux).
  • Le premier support est échangé avec le second, qui à son tour correspond au support en position k.

Dans le deuxième cas, le support en position kpeut avoir échangé avec l'un de ses voisins. Dans l'un ou l'autre de ces deux derniers cas, la chaîne entre la première (éventuellement nouvelle) parenthèse et la parenthèse qui a commencé en position kdoit être modifiée en une chaîne équilibrée, tout comme la chaîne composée de tout ce qui suit k.

Cela signifie qu'une approche de programmation dynamique peut être utilisée. Puisqu'un crochet échangé n'a pas besoin d'être échangé à nouveau, nous devons seulement considérer les sous-chaînes contiguës, ainsi que les sous-séquences formées en supprimant le deuxième caractère et / ou l'avant-dernier caractère d'une telle sous-chaîne. Par conséquent, il n'y a que O (n 2 ) sous-séquences que nous devons examiner. Chacun d'eux a O (n) façons possibles de faire correspondre (ou supprimer) la première parenthèse, donc l'algorithme serait O (n 3 ) dans les conditions ci-dessus.

La structure des données

La pile de droite comprend les crochets de la chaîne d'origine, avec deux octets par crochet. La première entrée détermine l'intégralité du crochet et est choisie de telle sorte que les crochets correspondants ont une différence d'exactement 1. La deuxième entrée détermine uniquement s'il s'agit d'un crochet d'ouverture ou d'un crochet de fermeture: cela détermine le nombre de modifications nécessaires pour que deux crochets correspondent. L'un et l'autre. Aucun zéro implicite en dessous de ceci n'est jamais rendu explicite, afin que nous puissions utiliser []pour obtenir la longueur totale de cette chaîne.

Chaque sous-chaîne considérée est représentée par deux nombres compris entre 0 et 2n: un pour la position de début et un pour la fin. L'interprétation est la suivante:

  • Une sous-chaîne commençant à 2kcommencera à la position k(indexée 0) et le deuxième caractère n'est pas supprimé.
  • Une sous-chaîne commençant à 2k+1commencera à la position ket le deuxième caractère est supprimé car il a été échangé vers la gauche.
  • Une sous-chaîne se terminant à 2kse terminera juste avant la position k(c'est-à-dire que la plage est incluse à gauche et à droite uniquement.)
  • Une sous-chaîne se terminant à 2k-1se terminera juste avant la position k, et l'avant-dernier caractère est supprimé car il a été inversé à droite.

Certaines plages ( kà k+1, 2k+1à 2k+1, 2k+1à 2k+3et 2k+1à 2k+5) n'ont aucun sens physique. Certains d'entre eux apparaissent de toute façon comme des valeurs intermédiaires, car c'est plus facile que d'ajouter des vérifications supplémentaires pour les éviter.

La pile de gauche stocke le nombre de modifications nécessaires pour convertir chaque sous-chaîne en une chaîne équilibrée. La distance d'édition pour l'intervalle(x,y) est stockée en profondeur x + y(y-1)/2.

Pendant la boucle interne, des entrées sont ajoutées au-dessus de la pile de gauche pour indiquer les mouvements possibles. Ces entrées sont longues de 5 octets. Compter à partir du haut, les chiffres sont d+1, y1, x1, y2, x2, où le mouvement coûte dpas de modifier et divise la sous - chaîne en(x1,y1) et (x2,y2).

Le code

Description à venir. Pour l'instant, voici ma copie de travail du code. Certains commentaires peuvent ne pas correspondre à la terminologie.

# 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
la source
2

Haskell , 797 octets

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

Essayez-le en ligne!

Roman Czyborra
la source
Hier, j'ai lu ici que la prime ne se terminerait pas avant demain, donc je voulais contester qu'une implémentation appliquant l' algorithme en.wikipedia.org/wiki/… calcule des valeurs plus correctes que l'heuristique rétinienne beaucoup plus rapide actuelle!
Roman Czyborra
Non, ce n'est pas digne du prix après tout parce que j'ai extrapolé à tort que son grokking les 18 caractères distants 4 en 2400s à 800MHz grokerait également les 22 caractères distants 9 également proches de 3600s, ce qui malheureusement ne le serait pas.
Roman Czyborra