Сбалансировать скобки

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
@ Биджан Нет, это не сильно облегчит, и, кроме того, скоро у Brain-Flak скоро будет день рождения!
Павел
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 Спасибо! Я не уверен, что это всегда работает, но это работает, по крайней мере, для предоставленных тестовых случаев и нескольких крайних случаев, которые я придумал
math
2
Учитывая это [{][}] [] [[][][][][][]] [][][][][][][][][][][][], вы можете просто поменять местами }первую пару скобок и сбалансировать их. Интервал используется, чтобы сделать ввод немного более читабельным. Вы вывели 3, но это действительно должно быть одно.
Энтони Фам
@AnthonyPham Спасибо! Я думаю, я знаю, почему это не работает, я попытаюсь найти умный способ исправить это
математика наркоман
Еще более странно то, что [{]}возвращается 1, но [{][]}возвращается 2.
Энтони Фам
12

Brain-Flak , 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
  {}<>([[]{}])

}{}

(([]){<{}{}>([])}{}<>){({}[()]<{}>)}{}({}<>)
Nitrodon
источник
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/… , вычисляет больше правильных значений, чем текущий, намного быстрее эвристики Retina!
Роман Чиборра
Нет, это, в конце концов, не заслуживает награды, потому что я ошибочно экстраполировал, что он поглотил бы 18 символов, удаленных от 4, в 2400-х при 800 МГц, а также поглотил бы 22 символа, удаленных от 9, одинаково близких к 3600-м, что, к сожалению, не получилось бы.
Роман Чиборра