Seimbangkan Kurung

24

Tujuan Anda: Diberikan serangkaian tanda kurung, output Jarak Damerau-Levenshtein minimum diperlukan untuk mengubah string input menjadi string di mana tanda kurung seimbang.

Memasukkan

String input hanya akan berisi tanda kurung dan tidak ada karakter lain. Artinya, itu adalah kombinasi dari salah satu karakter di (){}[]<>. Anda dapat mengambil input baik sebagai string atau array karakter. Anda tidak boleh membuat asumsi lain tentang string input; mungkin panjangnya sewenang-wenang (hingga ukuran maksimum yang didukung oleh bahasa Anda), mungkin kosong, kurung mungkin sudah seimbang, dll.

Damerau-Levenshtein Jarak

Jarak Damerau-Levenshtein antara dua string adalah jumlah minimum penyisipan, penghapusan, pergantian karakter tunggal, dan transposisi (swapping) dari dua karakter yang berdekatan.

Keluaran

Output harus Jarak Damerau-Levenshtein minimum antara string input dan string di mana tanda kurung cocok. Output harus berupa angka , bukan string seimbang yang dihasilkan.

Sepasang tanda kurung dianggap "cocok" jika tanda kurung buka dan tutup berada dalam urutan yang benar dan tidak memiliki karakter di dalamnya, seperti

()
[]{}

Atau jika setiap sub-elemen di dalamnya juga cocok.

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

Sub-elemen juga dapat disarangkan beberapa lapisan.

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

(Terima kasih kepada @DJMcMayhem untuk definisinya)

Uji Kasus

Input                   Possible Balanced       Output

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

(Terima kasih kepada @WheatWizard untuk memecahkan setengah dari kasus uji)

Ini adalah , byte terkecil menang!

Kiriman Anda harus dapat diuji, artinya harus menghasilkan hasil untuk setiap kasus uji dalam waktu tidak lebih dari satu jam.

Pavel
sumber
9
Seimbangkan tanda kurung Anda: P
Christopher
3
Saya akan terkejut jika kita bahkan akan melihat jawaban non-bruteforce yang benar untuk tantangan ini.
orlp
5
@SIGSEGV jawaban yang 1. Tidak peduli apakah Anda menyeimbangkan seperti [<>]atau []<>atau<>
Nathan Merrill
3
@ Bijan Nah, itu tidak akan membuatnya lebih mudah, dan selain itu, ulang tahun Brain-Flak akan segera hadir!
Pavel
3
@ qwr Mengapa ada batasan? Batas waktu hanya berlaku untuk kasus uji yang diberikan, untuk input besar program Anda dapat mengambil semua waktu di dunia.
Pavel

Jawaban:

13

Retina, 254 252 264 248 240 232 267 byte

Terima kasih kepada @AnthonyPham, @officialaimm, dan @MistahFiggins karena telah menunjukkan 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

Cobalah secara Online!

Solusi gaya non-brute! Ini berfungsi untuk semua kasus uji, dan bahkan menemukan kesalahan dalam satu.

-2 byte terima kasih kepada @MartinEnder ( ${4}ke $+)

+12 byte ke akun untuk kasus swapping tambahan

-16 byte dengan memanfaatkan kelas karakter dengan lebih baik

-8 byte dengan menghapus batasan yang tidak perlu pada swapping. Ini juga memperbaiki bug :)

-10 byte dengan menggabungkan logika swapping ke dalam satu regex tunggal

+2 byte ke akun untuk swap berurutan

+ Banyak untuk berbagai perbaikan bug **

Penjelasan:

T`[]()`:;'"digunakan untuk mengganti jenis braket khusus untuk kenyamanan. Pertama, kami secara rekursif mengganti semua tanda kurung yang cocok dengan -,AB atau 69bergantung pada apakah berdekatan atau tidak.

Kemudian, "swapping" yang bermanfaat dilakukan dengan menghapus tanda kurung yang baru cocok dan menambahkan a 1ke awal string. Kami juga mengganti- dengan string kosong, karena hanya digunakan untuk swapping di atas.

Selanjutnya, kami mencoba "penggantian" dengan menghapus pasangan tanda kurung yang tidak cocok yang tidak tumpang tindih tanda kurung yang sudah cocok dan menambahkan 1 ke string.

Akhirnya, \W|6B|1hitung kurung tunggal yang tersisa ditambah jumlah1 s.

** Saat ini saya sedang mengerjakan versi yang lebih pendek yang menggunakan fitur pemisahan garis Retina, meskipun saya mengalami masalah yang cukup besar sehingga mungkin butuh waktu cukup lama.

pecandu matematika
sumber
Itu ${4}bisa disingkat $+. Saya sangat ingin tahu mengapa ini dijamin bekerja.
Martin Ender
@ MartinEnder Terima kasih! Saya tidak yakin bahwa itu selalu berhasil, tetapi itu bekerja setidaknya untuk kasus uji yang disediakan, dan beberapa kasus tepi yang saya buat
pecandu matematika
2
Diberikan [{][}] [] [[][][][][][]] [][][][][][][][][][][][], Anda cukup menukar bagian }dalam pasangan kurung pertama dan membuatnya seimbang. Spasi digunakan untuk membuat input sedikit lebih mudah dibaca. Anda menghasilkan 3 tetapi harus benar-benar satu.
Anthony Pham
@AnthonyPham Terima kasih! Saya rasa saya tahu mengapa itu tidak berhasil, saya akan mencoba menemukan cara cerdas untuk memperbaikinya
pecandu matematika
Bahkan yang lebih aneh adalah yang [{]}mengembalikan 1 tetapi [{][]}mengembalikan 2.
Anthony Pham
12

Brain-Flak , 1350 byte

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

Cobalah online!

Dengan perbandingan kecepatan konstan dan pointer dereferencing, algoritma ini adalah O (n 3 ). Sayangnya, Brain-Flak tidak memiliki keduanya, jadi program ini berjalan dalam waktu O (n 5 ) sebagai gantinya. Kasing uji terpanjang membutuhkan waktu sekitar 15 menit.

Menyederhanakan hasil

Untuk melihat bahwa algoritma saya berfungsi, kami perlu menunjukkan beberapa hasil yang mengurangi ruang pencarian secara signifikan. Hasil ini bergantung pada fakta bahwa target adalah seluruh bahasa, bukan hanya satu string tertentu.

  • Tidak diperlukan penyisipan. Sebagai gantinya, Anda bisa menghapus braket yang akhirnya cocok dengan karakter yang dimasukkan.

  • Anda tidak perlu menghapus braket, lalu menukar kedua tetangganya. Untuk melihat ini, asumsikan wlog bahwa braket yang dihapus adalah (, jadi kami mentransformasikannya a(cke cadalam dua langkah. Dengan mengubah cdan memasukkan salinan, kita dapat mencapai ca()dalam dua langkah tanpa swap. (Penyisipan ini kemudian dapat dihapus oleh aturan di atas.)

  • Braket yang sama tidak perlu ditukar dua kali. Ini adalah fakta standar tentang jarak Damerau-Levenshtein secara umum.

Hasil penyederhanaan lain yang tidak saya gunakan, karena menghitungnya akan membutuhkan biaya byte:

  • Jika dua tanda kurung bertukar, dan mereka tidak cocok satu sama lain, pertandingan akhirnya ke masing-masing tanda kurung tidak akan pernah diubah atau ditukar.

Algoritma

Ketika string apa pun direduksi menjadi string seimbang, salah satu dari yang berikut ini benar:

  • Braket pertama dihapus.
  • Braket pertama tetap berada di tempatnya dan cocok dengan braket pada beberapa posisi k(mungkin setelah mengubah satu atau keduanya).
  • Braket pertama ditukar dengan yang kedua, yang pada gilirannya cocok dengan braket pada posisi k.

Dalam kasus kedua, braket pada posisi kmungkin telah bertukar dengan salah satu tetangganya. Dalam salah satu dari dua kasus terakhir, string antara braket pertama (mungkin yang baru) dan braket yang dimulai pada posisi kharus diedit ke string seimbang, seperti halnya string yang terdiri dari semuanya setelahnya k.

Ini berarti bahwa pendekatan pemrograman dinamis dapat digunakan. Karena braket bertukar tidak perlu ditukar lagi, kita hanya perlu mempertimbangkan substring yang berdekatan, serta selanjutnya dibentuk dengan menghapus karakter kedua dan / atau karakter kedua dari belakang substring tersebut. Karenanya, hanya ada O (n 2 ) yang harus kita perhatikan. Masing-masing dari mereka memiliki O (n) cara yang mungkin untuk mencocokkan (atau menghapus) braket pertama, sehingga algoritma akan menjadi O (n 3 ) di bawah kondisi di atas.

Struktur data

Tumpukan yang tepat termasuk tanda kurung dari string asli, dengan dua byte per braket. Entri pertama menentukan seluruh braket, dan dipilih sedemikian rupa sehingga tanda kurung yang cocok memiliki perbedaan tepat 1. Entri kedua hanya menentukan apakah braket pembuka atau braket penutup: ini menentukan berapa banyak perubahan yang diperlukan untuk mencocokkan dua tanda kurung satu sama lain. Tidak ada nol tersirat di bawah ini yang pernah dibuat eksplisit, sehingga kita dapat menggunakan[] untuk mendapatkan panjang total dari string ini.

Setiap substring yang dipertimbangkan diwakili oleh dua angka dalam kisaran 0 hingga 2n: satu untuk posisi awal, dan satu untuk akhir. Interpretasinya adalah sebagai berikut:

  • Substring yang dimulai pada 2kakan mulai dari posisik (0-diindeks), dan karakter kedua tidak dihapus.
  • Substring yang dimulai pada 2k+1akan mulai dari posisik , dan karakter kedua dihapus karena telah ditukar kiri.
  • Substring yang berakhir pada 2kakan berakhir tepat sebelum posisi k(yaitu, rentang inklusif kiri dan eksklusif kanan.)
  • Substring yang berakhir pada 2k-1akan berakhir tepat sebelum posisi k, dan karakter kedua dari belakang dihapus karena telah ditukar dengan benar.

Beberapa rentang ( kke k+1, 2k+1ke 2k+1, 2k+1ke 2k+3, dan 2k+1untuk 2k+5) tidak masuk akal secara fisik. Beberapa dari mereka muncul sebagai nilai perantara, karena lebih mudah daripada menambahkan pemeriksaan tambahan untuk menghindarinya.

Stack kiri menyimpan jumlah suntingan yang diperlukan untuk mengubah setiap substring menjadi string yang seimbang. Jarak edit untuk interval (x,y)disimpan pada kedalaman x + y(y-1)/2.

Selama loop dalam, entri ditambahkan di atas tumpukan kiri untuk menunjukkan gerakan mana yang mungkin. Entri ini panjangnya 5 byte. Menghitung dari atas, jumlahnya d+1, y1, x1, y2, x2, di mana langkah itu biaya dmengedit langkah dan membagi substring dalam (x1,y1)dan (x2,y2).

Kode

Deskripsi yang akan datang. Untuk saat ini, inilah salinan kode saya yang berfungsi. Beberapa komentar mungkin tidak konsisten dengan terminologi.

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

Cobalah online!

Roman Czyborra
sumber
Kemarin saya baca di sini bahwa hadiah itu tidak akan berakhir sebelum besok karena itu saya ingin kontes bahwa penerapan menerapkan algoritma en.wikipedia.org/wiki/… menghitung nilai yang lebih benar daripada heuristik Retina saat ini yang jauh lebih cepat!
Roman Czyborra
Tidak, ini bukan hadiah yang pantas karena saya keliru memperkirakan bahwa mengacak-acak 18 karakter jauh 4 dalam 2400s @ 800MHz juga akan grok 22 karakter jauh 9 sama dekat dengan 3600 yang sayangnya tidak mau.
Roman Czyborra