Parantezleri Dengeleyin


24

Hedefiniz: Bir dizi parantez verildiğinde , giriş dizesini parantezlerin dengeli olduğu bir dize haline getirmek için gereken minimum Damerau-Levenshtein Mesafe değerini verin .

Giriş

Giriş dizesi yalnızca parantez içerecek ve başka karakter içermeyecek. Yani, içindeki karakterlerden herhangi birinin bir birleşimidir (){}[]<>. Girdiyi bir dize veya bir karakter dizisi olarak alabilirsiniz. Giriş dizesi hakkında başka bir varsayımda bulunamazsınız; isteğe bağlı olarak uzun olabilir (diliniz tarafından desteklenen maksimum boyuta kadar), boş olabilir, parantezler dengeli olabilir, vb.

Damerau-Levenshtein Uzaklığı

İki dizi arasındaki Damerau-Levenshtein Uzaklığı, iki bitişik karakterin minimum yerleştirme, silme, tek karakterli yer değiştirme ve yer değiştirmesidir (yer değiştirme).

Çıktı

Çıkış, giriş dizesi ile parantezlerin eşleştirildiği bir dize arasındaki minimum Damerau-Levenshtein Mesafesi olmalıdır. Çıktı , elde edilen dengeli dize değil bir sayı olmalıdır .

Açma ve kapama parantezleri doğru sıradaysa ve içlerinde karakterleri yoksa, bir parantez bir çift parantez olarak kabul edilir.

()
[]{}

Veya içindeki her alt öğe aynı zamanda eşleşirse.

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

Alt elemanlar ayrıca birkaç kat derinlikte yuvalanabilir.

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

(Tanım için @DJMcMayhem'e teşekkürler)

Test Kılıfları

Input                   Possible Balanced       Output

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

(Test vakalarının yarısını çözmek için @WheatWizard'a teşekkürler)

Bu , en az bayt kazanıyor!

Başvurularınız test edilebilir olmalıdır, yani her test durumu için bir saatten fazla bir sonuç vermemelidir.


9
Kendi braketlerinizi dengeleyin: P
Christopher

3
Bu zorluğa tek bir doğru brüt olmayan kuvvet cevabı göreceksek bile şaşırırım.
orlp

5
@SIGSEGV bunu cevaplamak Bu gibi onu dengelemek ister önemli değil 1'dir [<>]ya []<>ya<>
Nathan Merrill

3
@Bijan Nah, bunu daha kolay hale getirmeyecek ve ayrıca Brain-Flak'ın doğum günü yakında geliyor!
Pavel

3
@ qwr Neden bir limitiniz var? Zaman sınırı yalnızca verilen test durumları için geçerlidir, büyük girdiler için programınız tüm dünyada sürebilir.
Pavel

Yanıtlar:


13

Retina, 254 252 264 248 240 232 267 bayt

Hata bildirmek için @AnthonyPham, @officialaimm ve @MistahFiggins için teşekkür ederiz

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

Çevrimiçi Deneyin!

Kaba olmayan kuvvet çözümü! Tüm test durumları için işe yarar ve hatta birinde bir hata bile bulur.

@MartinEnder ( ${4}to $+) sayesinde -2 bayt

Ek değiştirme durumlarını hesaba katarak +12 bayt

Karakter sınıflarını daha iyi kullanarak -16 bayt

Değiştirme sırasında gereksiz bir kısıtlamayı kaldırarak -8 bayt. Bu da bir hata düzeltildi :)

Değiştirme mantığını tek bir regex'te birleştirerek -10 bayt

Ardışık değiş tokuşları hesaba katarak +2 bayt

+ çeşitli hata düzeltmeleri için birçok **

Açıklama:

T`[]()`:;'"kolaylık sağlamak için özel braket tiplerinin yerine kullanılır. İlk olarak, ardışık Tüm eşleşen parantez yerine -, ABya 69da bitişik olup olmadığına bağlı olarak değişir.

Daha sonra, yeni eşleştirilen parantezlerin çıkarılması 1ve dizenin başına bir a eklenmesiyle faydalı "takas etme" gerçekleştirilir . Aynı zamanda -sadece yukarıdaki takas için kullanıldığı için boş dizeyle değiştiririz.

Daha sonra, zaten eşleşmiş parantezlerle üst üste gelmeyen eşleştirilmiş parantez çiftlerini kaldırarak 1ve dizeye bir a ekleyerek "değiştirmeleri" deniyoruz .

Son olarak, \W|6B|1kalan tüm parantezleri artı 1s sayısını sayar .

** Şu ​​anda, Retina'nın çizgi bölme özelliklerini kullanan daha kısa bir sürüm üzerinde çalışıyorum, ancak oldukça uzun bir süre alabilmem için önemli bir sorunla karşılaştım.


Bu ${4}kısaltılabilir $+. Bunun neden işe yaradığını garanti ettiğimi çok merak ediyorum.
Martin Ender

@MartinEnder Teşekkürler! Her zaman çalıştığından emin değilim , ancak en azından sağlanan test durumları için ve çalıştığım birkaç kenarlık davaları için işe yarıyor
matematik junkie

2
Verilen [{][}] [] [[][][][][][]] [][][][][][][][][][][][], }ilk parantez çiftinin içini değiştirebilir ve dengeli olmasını sağlayabilirsiniz. Boşluk, girişi biraz daha okunaklı hale getirmek için kullanılır. Sen 3'ü çıkardın ama gerçekten biri olmalı.
Anthony Pham

@AnthonyPham Teşekkürler! Sanırım neden işe yaramadığını biliyorum, düzeltmek için zekice bir yol bulmaya çalışacağım
matematik bağımlısı

En garip olanı bile [{]}1 döndürür ancak 2 [{][]}döndürür.
Anthony Pham

12

Brain-Flak , 1350 bayt

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

Çevrimiçi deneyin!

Sabit hızlı karşılaştırmalar ve işaretçiyi kaldırma ile bu algoritma O (n, 3 ). Ne yazık ki, Brain-Flak bunların hiçbirine sahip değil, bu nedenle bu program O (n 5 ) zamanlarında çalışıyor. En uzun test durumu yaklaşık 15 dakika sürer.

Basitleştirme sonuçları

Algoritmamın çalıştığını görmek için arama alanını önemli ölçüde azaltan bazı sonuçlar göstermemiz gerekiyor. Bu sonuçlar, hedefin sadece belirli bir dize yerine bütün bir dil olduğu gerçeğine dayanmaktadır.

  • Eklemeye gerek yok. Bunun yerine, eklenen karakterin sonunda eşleşeceği braketi kaldırabilirsiniz.

  • Bir braketi çıkarmanız gerekmeyecek, ardından iki komşusunu yer değiştirmeyeceksiniz. Bunu görmek için, kaldırılan ayraç olduğunu wlog varsayalım (biz dönüştüren, böylece a(ciçin caiki adımda. cBir kopyayı değiştirip ekleyerek, ca()takas olmadan iki adımda ulaşabiliriz . (Bu ekleme, yukarıdaki kural tarafından kaldırılabilir.)

  • Aynı braketin asla iki kez değiştirilmesine gerek kalmayacak. Bu, genel olarak Damerau-Levenshtein mesafesi hakkında standart bir gerçektir.

Kullanmadığım başka bir basitleştirici sonuç, çünkü bunların muhasebesi baytlara mal olacak:

  • İki parantez değiştirilirse ve bunlar birbiriyle eşleşmezse, bu parantezlerin her biriyle sonuçlanan eşleşme asla değiştirilmez veya değiştirilmez.

Algoritması

Herhangi bir dize dengeli bir dizeye indirgendiğinde, aşağıdakilerden biri geçerli olacaktır:

  • İlk braket silinir.
  • İlk braket olduğu yerde kalır ve braketi bir konumda tutar k(muhtemelen bir tanesini veya her ikisini de değiştirdikten sonra).
  • İlk braket, ikincisi ile değiştirilir ve bu sırada braket pozisyonda kalır k.

İkinci durumda, pozisyondaki braket kkomşularından biriyle değiştirilmiş olabilir. İkinci iki durumdan birinde, (muhtemelen yeni olan) ilk parantez ve pozisyonda başlayan parantez arasındaki kdize, daha sonra her şeyden oluşan ipte olduğu gibi dengeli bir ipte düzenlenmelidir k.

Bu, dinamik bir programlama yaklaşımının kullanılabileceği anlamına gelir. Değiştirilen bir braketin tekrar takılmasının gerekmemesi nedeniyle, sadece bitişik alt dizilerin yanı sıra, ikinci karakter ve / veya penüren karakterin böyle bir alt dizilimden çıkarılmasıyla oluşturulan alt dizileri de dikkate almamız gerekir. Dolayısıyla, bakmamız gereken sadece O (n 2 ) alt dizileri vardır. Bunların her biri, algoritma O (n olacak, böylece, birinci dirsek ile eşleşmesi (ya da silme) O (n) olanağa sahiptir 3 yukarıdaki koşullar altında).

Veri yapısı

Sağ istif, orijinal diziden gelen braketleri içerir, braket başına iki byte. İlk giriş tüm braketi belirler ve eşleşen braketler tam olarak 1 farkına sahip olacak şekilde seçilir. İkinci giriş yalnızca bir açılış braketi mi yoksa bir kapatma braketi mi olduğunu belirler: bu, iki braketin eşleşmesi için kaç değişikliğin yapılacağını belirler. herbiri. Bunun altındaki hiçbir örtülü sıfır açık değildir, bu nedenle []bu dizenin toplam uzunluğunu elde etmek için kullanabiliriz .

İncelenen her alt dize, 0 ila 2n aralığında iki sayı ile temsil edilir: biri başlangıç ​​pozisyonu için diğeri bitiş için. Yorum şu şekildedir:

  • Başlayan bir alt 2kpozisyon k(0 indeksli) pozisyonunda başlayacak ve ikinci karakter kaldırılmaz.
  • Başlayan bir alt halka 2k+1pozisyonda başlayacak kve ikinci karakter sola kaydırıldığı için kaldırılmıştır.
  • 2kSonunda biten bir alt dize, pozisyondan hemen önce sona erecektir k(yani, aralık sol dahil ve sağa özeldir).
  • 2k-1Sonunda biten bir alt halka, pozisyondan hemen önce sona erecek ve ksağa kaydırılmış olması nedeniyle sondan bir önceki karakter silinecektir.

Bazı aralıklar ( küzere k+1, 2k+1için 2k+1, 2k+1için 2k+3, ve 2k+1karşı 2k+5) fiziksel bir anlam ifade etmez. Bunlardan bazıları yine de ara değerler olarak gözüküyor, çünkü bunlardan kaçınmak için ek kontroller eklemekten daha kolay.

Sol yığın, her alt dizgiyi dengeli bir dizgeye dönüştürmek için gereken düzenleme sayısını saklar. Aralık için düzenleme mesafesi (x,y)derinlikte saklanır x + y(y-1)/2.

İç döngü sırasında, hangi hareketlerin mümkün olduğunu göstermek için sol yığının üstüne girişler eklenir. Bu girişler 5 bayttır. Üstten Sayma, sayılardır d+1, y1, x1, y2, x2, taşıma maliyeti nerede ddüzenleme adımlarını ve bölünmeler içine alt dizeyi (x1,y1)ve (x2,y2).

Kod

Açıklama gelmek üzere. Şimdilik, işte kodun çalışma kopyası. Bazı yorumlar terminoloji ile tutarsız olabilir.

# 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 bayt

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

Çevrimiçi deneyin!


Dün burada ödülün yarından önce bitmeyeceğini okudum, bu nedenle en.wikipedia.org/wiki/… algoritmasını uygulayan bir uygulamanın mevcut çok daha hızlı olan Retina sezgiselinden daha doğru değerler elde ettiğini itiraz etmek istedim !
Roman Czyborra

Hayır, sonuçta bu ödüle layık değil çünkü yanlış bir şekilde, 2400’lerde @ 800MHz’de 4 karakterin 4 karakterinin yer almasının, ne yazık ki, 3600’lere eşit derecede yakın olmayan 22 karakterin 9’unu sıkıştıracağını tahmin ettim.
Roman Czyborra
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.