Збалансуйте дужки


24

Ваша мета: Враховуючи рядок дужок, виведіть мінімальний відстань Дамерау-Левенштейн, необхідний для перетворення вхідного рядка в рядок, де дужки врівноважені.

Вхідні дані

Вхідний рядок буде містити лише дужки та ніяких інших символів. Тобто це поєднання будь-якого із персонажів у (){}[]<>. Ви можете приймати введення як рядок, так і масив символів. Ви не можете робити жодних інших припущень щодо рядка введення; він може бути довільним довгим (до максимального розміру, який підтримує ваша мова), він може бути порожнім, дужки вже можуть бути врівноваженими тощо.

Дамерау-Левенштейн Відстань

Відстань Дамерау-Левенштейн між двома рядками - це мінімальна кількість вставок, вилучень, односимвольних підстановок та переміщень (заміни) двох суміжних символів.

Вихідні дані

Вихід повинен бути мінімальним Відстань Дамерау-Левенштейн між вхідним рядком і рядком, в якому дужки узгоджуються. Вихід повинен бути числом , а не отриманим збалансованим рядком.

Пара дужок вважається "зібраною", якщо дужки, що відкриваються і закриваються, є в правильному порядку і не містять символів всередині них, наприклад

()
[]{}

Або якщо кожен під-елемент всередині нього також відповідає.

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

Піделементи також можуть бути вкладені в кілька шарів глибиною.

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

(Дякуємо @DJMcMayhem за визначення)

Випробування

Input                   Possible Balanced       Output

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

(Дякуємо @WheatWizard за вирішення половини тестових випадків)

Це , найменше виграш байтів!

Ваші подання повинні бути перевірені, це означає, що вони повинні давати результат для кожного тестового випадку не більше години.


9
Збалансуйте власні дужки: P
Крістофер

3
Я буду здивований, якщо ми навіть побачимо одну правильну відповідь, що не стосується грубої сили, на цей виклик.
orlp

5
@SIGSEGV відповідь на це 1. Не має значення , є чи ви збалансувати його , як [<>]і []<>чи<>
Натан Меррілл

3
@Bijan Nah, це не зробить це набагато простіше, і до того ж незабаром настає день народження Брейн-Флака!
Павло

3
@qwr Чому обмеження? Обмеження часу застосовується лише для наведених тестових випадків, оскільки для великих програм ваша програма може зайняти весь час у світі.
Павло

Відповіді:


13

Сітківка, 254 252 264 248 240 232 267 байт

Дякуємо @AnthonyPham, @officialaimm та @MistahFiggins за вказівку на помилки

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

Спробуйте в Інтернеті!

Рішення без грубої сили! Він працює для всіх тестових випадків і навіть знайшов помилку в одному.

-2 байти завдяки @MartinEnder ( ${4}до $+)

+12 байт для обліку додаткових випадків заміни

-16 байт шляхом кращого використання класів символів

-8 байт, видаливши зайве обмеження на заміну. Це також виправило помилку :)

-10 байт, поєднуючи логіку заміни в єдиний регулярний вираз

+2 байти для обліку послідовних свопів

+ багато для різних виправлень помилок **

Пояснення:

T`[]()`:;'"використовується для заміни спеціальних типів кронштейнів для зручності. По- перше, ми рекурсивно замінити всі збіглися дужки з -, ABабо в 69залежності від того, чи є вони суміжними чи ні.

Потім корисна «заміна» виконується шляхом видалення щойно збігнутих дужок та додавання a 1на початку рядка. Ми також замінюємо -порожній рядок, оскільки він просто використовувався для вищезазначеної заміни.

Далі ми намагаємося "замінити", видаливши пари невідповідних дужок, які не перекриваються вже узгодженими дужками, і додавши 1до рядка.

Нарешті, \W|6B|1підраховує всі інші дужки плюс кількість 1s.

** Зараз я працюю над коротшою версією, яка використовує функції розбиття лінії Retina, хоча у мене виникла значна проблема, тому це може зайняти досить довго.


Це ${4}можна скоротити до $+. Мені дуже цікаво, чому це гарантовано працює.
Мартін Ендер

@MartinEnder Дякую! Я не впевнений, що це завжди працює, але це працює принаймні для наданих тестових випадків, і пару крайових випадків, які я придумав
математика наркоман

2
Враховуючи [{][}] [] [[][][][][][]] [][][][][][][][][][][][], ви можете просто поміняти }всередині першої пари дужок і збалансувати його. Пробіл використовується для того, щоб зробити введення трохи читабельнішим. Ви вивели 3, але це дійсно має бути один.
Ентоні Фам

@AnthonyPham Дякую! Я думаю, я знаю, чому це не працює, я спробую знайти розумний спосіб виправити це
математика наркоман

Ще дивніше те, що [{]}повертає 1, але [{][]}повертає 2.
Ентоні Фем

12

Мозок-Флак , 1350 байт

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

Спробуйте в Інтернеті!

При порівнянні з постійною швидкістю та перенаправленням покажчика цей алгоритм дорівнює O (n 3 ). На жаль, у Brain-Flak немає нічого з цього, тому ця програма працює замість O (n 5 ). Найдовший тестовий випадок займає близько 15 хвилин.

Спрощення результатів

Щоб побачити, що мій алгоритм працює, нам потрібно показати деякі результати, які значно скорочують простір пошуку. Ці результати залежать від того, що цільовою є ціла мова, а не лише одна конкретна рядок.

  • Вставки не потрібні. Натомість ви можете просто зняти дужку, якій вставлений символ у підсумку відповідатиме.

  • Ніколи не потрібно буде знімати кронштейн, а потім поміняти місцями двох своїх сусідів. Щоб переконатися в цьому, припустимо , що без втрати спільності , віддалений кронштейн (, тому ми трансформуємо a(cдо caв два етапи. Змінюючи cта вставляючи копію, ми можемо досягти ca()двох кроків без заміни. (Потім цю вставку можна буде видалити за вищенаведеним правилом.)

  • Один і той же кронштейн ніколи не потрібно буде міняти двічі. Це стандартний факт про відстань Дамерау-Левенштайн взагалі.

Ще один спрощуючий результат, який я не використовував, оскільки облік їх коштував би байтів:

  • Якщо дві дужки поміняти місцями, і вони не збігаються між собою, можлива відповідність кожній з цих дужок ніколи не буде змінена або замінена.

Алгоритм

Коли будь-який рядок зводиться до врівноваженого рядка, буде істинним одне з наступних:

  • Перша дужка видаляється.
  • Перша дужка залишається там, де вона є, і відповідає дужці в деякому положенні k(можливо, після зміни одного або обох).
  • Перша дужка поміняється на другу, що в свою чергу відповідає дужці в положенні k.

У другому випадку кронштейн у положенні, kможливо, помінявся одним із своїх сусідів. В будь-якому з останніх двох випадків рядок між (можливо, нещодавно) першою дужкою та дужкою, що почалася в положенні, kповинен бути відредагований у врівноважену рядок, як і рядок, що складається з усього, що йде після k.

Це означає, що може використовуватися динамічний підхід до програмування. Оскільки замінений кронштейн не потрібно повторно міняти, нам потрібно розглянути лише суміжні підрядки, а також підрядки, утворені шляхом видалення другого символу та / або передостаннього символу з такої підрядки. Отже, є лише O (n 2 ) послідовності, на які нам потрібно звернути увагу. Кожен з них має O (n) можливих способів узгодження (або видалення) першого дужки, тому алгоритм був би O (n 3 ) за вищезазначених умов.

Структура даних

Правий стек містить дужки з початкового рядка з двома байтами на дужку. Перший запис визначає всю дужку і вибирається таким, щоб відповідні дужки мали різницю рівно 1. Другий запис визначає лише, чи це відкривальна дужка, або дужка, що закривається: це визначає, скільки змін потрібно, щоб дві дужки відповідали один одного. Жодні неявні нулі нижче цього ніколи не стають явними, так що ми можемо використовувати []для отримання загальної довжини цього рядка.

Кожна підрядок, що розглядається, представлена ​​двома числами в діапазоні від 0 до 2n: одне для початкової позиції і одне для кінця. Інтерпретація така:

  • Підрядка, що починається з 2kпочатку, починається з позиції k(індексується 0), а другий символ не видаляється.
  • Підрядка, що починається з 2k+1початку, починається з позиції k, а другий символ видаляється через розміщення зліва.
  • Підрядка, що закінчується на 2kволі, закінчується безпосередньо перед позицією k(тобто діапазон включений зліва та праворуч.)
  • Підрядка, що закінчується на 2k-1волі, закінчується безпосередньо перед позицією k, і передостанній символ видаляється через те, що вони були поміщені праворуч.

Деякі діапазони ( kдо k+1, 2k+1до 2k+1, 2k+1до 2k+3, і 2k+1в 2k+5) не мають ніякого фізичного сенсу. Деякі з них так чи інакше відображаються як проміжні значення, оскільки це простіше, ніж додавати додаткові перевірки, щоб уникнути їх.

У лівій стеці зберігається кількість редагувань, необхідних для перетворення кожної підрядки в збалансований рядок. Відстань редагування інтервалу (x,y)зберігається на глибині x + y(y-1)/2.

Під час внутрішнього циклу записи додаються над лівим стеком, щоб позначити, які можливі переміщення. Ці записи довжиною 5 байт. Підрахунок з вершини, номер d+1, y1, x1, y2, x2, де рух варто dредагувати кроки і вододіли подстроку в (x1,y1)і (x2,y2).

Кодекс

Опис в майбутньому. Наразі ось моя робоча копія коду. Деякі коментарі можуть суперечити термінології.

# 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 байт

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

Спробуйте в Інтернеті!


Вчора я прочитав тут, що щедро не закінчиться до завтра, тому я хотів оскаржити те, що реалізація, що застосовує алгоритм en.wikipedia.org/wiki/… , обчислює більш правильні значення, ніж нинішні, набагато швидші евристичні сітки!
Роман Чиборра

Ні, це не вартий премії, адже я помилково екстраполював, що його загравання 18 символів, віддалених 4 у 2400х @ 800 МГц, також сприймає 22 знаків, віддалених 9, настільки ж близьких до 3600, що, на жаль, не буде.
Роман Чиборра
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.