Считаем скобочки на Oracle SQL

в 19:41, , рубрики: oracle, sql, ненормальное программирование, Песочница, метки: , ,

Считаем скобочки на Oracle SQL
Все началось с того, что на сайте codeforces.ru в очередном Codeforces Round я увидел интересную задачку “Скобочная последовательность” и решать ее “неинтересным способом” никак не хотелось.

Вкратце условия задачи сводятся к нахождению в строке, состоящей только из символов «(», «)», «[» и «]», правильной cкобочной последовательности, содержащей как можно больше скобок «[».


Для начала превращаем нашу плоскую строчку в более удобное иерархическое представление:

with brackets as
(select '[[)]([][][][[()[()[()()]]]])]' b 
   from dual) 
select substr(b,level,1) q,
        level     curr_pos,
        level - 1 prev_pos 
   from brackets
connect by substr(b,level,1) is not null

В каждой строчке будет очередная скобка, позиция текущей скобки и позиция предыдущей скобки. С этим уже можно что-то сделать.

Осталось найти все правильные скобочные последовательности. Здесь начинается самое интересное.
Так как для определения правильности выражения у нас нет стека, в котором можно хранить скобочки, пришлось идти на “хитрость”.

Найдем все возможные последовательности, начинающиеся с «(» или «[» (остальные неправильные по умолчанию). Для каждой такой последовательности определим позицию первой и последней скобки и “уровень вложенности” последней скобки, т.е.:

«(» «1ый - уровень»
     «(»  «2ой – уровень»
      .. ... .. .. 
      «]»  «2ой – уровень»
.. ... .. .. .

  • Уровень увеличивается, если: скобка открывается, а перед ней ничего не было, либо была любая открывающаяся;
  • Уровень не изменяется, если скобка открывается, а перед ней была закрывающаяся, либо если скобка закрывается, а перед ней была открывающаяся;
  • Уровень уменьшается, если скобка закрывается, а перед ней была закрывающаяся;
with brackets as
(select '[[)]([][][][[()[()[()()]]]])]' b 
   from dual) , 
all_brackets as 
(select substr(b,level,1) q,
        level   curr_pos,
        level-1 prev_pos
   from brackets
connect by substr(b,level,1) is not null)

select replace(sys_connect_by_path(q,'-'), '-') str,      
              q,              
              connect_by_root(curr_pos) start_pos,
              curr_pos                  end_pos,
              sum( case
                   when (q = '(' or q = '[')
                    and (prior q is null)
                   then 1                        
                   when (q = '(' or q = '[')
                    and (prior q = '(' or prior q = '[')
                   then 1                          
                   when (q = '(' or q = '[')
                    and (prior q = ']' or prior q = ')')
                   then 0                           
                   when (q = ')' or q = ']') 
                    and (prior q = '(' or prior q = '[')
                   then 0                     
                   when (q = ')' or q = ']') 
                    and (prior q = ')' or prior q = ']')
                   then -1
                    end)
             over (partition by connect_by_root(curr_pos) 
                       order by connect_by_root(curr_pos), curr_pos) bracket_level              
         from all_brackets        
        connect by prior curr_pos = prev_pos
         start with q = '(' or q = '['

С помощью этих данных мы можем определить, скобку “вредителя”, которая делает из правильной последовательности неправильную. Для этого просмотрим все полученные последовательности, и при “появлении” очередной скобки, найдем предыдущую скобку, находящуюся на одном уровне с текущей (Спасибо Oracle за аналитическую функцию lag). Неправильная последовательность получается, если на одном из уровней возникает одна из следующих ситуаций:

  • закрывается закрытая скобка
  • круглая скобка закрывает квадратную
  • квадратная скобка закрывает круглую

Любая открывающаяся скобка на данной итерации не может “поломать” последовательность.

with brackets as
(select '[[)]([][][][[()[()[()()]]]])]' b 
   from dual) , 
all_brackets as 
(select substr(b,level,1) q,
        level     curr_pos,
        level - 1 prev_pos 
   from brackets
connect by substr(b,level,1) is not null),
brackets_comb as 
(select replace(sys_connect_by_path(q,'-'), '-') str,      
        q,              
        connect_by_root(curr_pos) start_pos,
        curr_pos                  end_pos,
        sum( case
             when (q = '(' or q = '[')
              and (prior q is null)
             then 1                        
             when (q = '(' or q = '[')
              and (prior q = '(' or prior q = '[')
             then 1                          
             when (q = '(' or q = '[')
              and (prior q = ']' or prior q = ')')
             then 0                           
             when (q = ')' or q = ']') 
              and (prior q = '(' or prior q = '[')
             then 0                     
             when (q = ')' or q = ']') 
              and (prior q = ')' or prior q = ']')
             then -1
              end)
       over (partition by connect_by_root(curr_pos) 
                 order by connect_by_root(curr_pos), curr_pos)  bracket_level                            
   from all_brackets
  connect by prior curr_pos = prev_pos
   start with q = '(' or q = '[') 
select start_pos,
       end_pos, 
       str,
       case 
        when q = ')' 
         and lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) = '('
        then 'ok'
        when q = '(' 
         and lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) = ')'
        then 'ok'
        when q = '(' 
         and lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) = ']'
        then 'ok'          
        when q = ']' 
         and lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) = '['
        then 'ok'
        when q = '[' 
         and lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) = ']'
        then 'ok'
        when q = '[' 
         and lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) = ')'
        then 'ok'
        when lag(q) over (partition by start_pos, bracket_level 
                              order by start_pos, bracket_level, end_pos) is null
         and bracket_level > 0
        then 'ok'
        else 'not ok!'
       end status    
  from brackets_comb bc  

Таким образом, для каждого множества последовательностей, начинающихся с одной позиции (start_pos) и упорядоченных по количеству скобок, мы нашли первую неверную последовательность.
В каждом таком множестве последовательностей “выкинем” последовательности после “неправильной”, и для всех оставшихся проверим, что открывшиеся скобки закрылись (для этого достаточно посчитать количество открывшихся и закрывшихся скобок каждого вида).
В результате останется найти ту единственную, а может и не единственную, её (нет, не девушку своей мечты, как вы могли подумать), скобочную последовательность с максимальным количеством «[».

Весь запрос:

with brackets as
(select '[[]])[([[]][[(]])]' b 
   from dual) , 
all_brackets as 
(select substr(b,level,1) q,
        level     curr_pos,
        level - 1 prev_pos 
   from brackets
connect by substr(b,level,1) is not null),
brackets_comb as 
(select replace(sys_connect_by_path(q,'-'), '-') str,      
        q,              
        connect_by_root(curr_pos) start_pos,
        curr_pos                  end_pos,
        sum( case
             when (q = '(' or q = '[')
              and (prior q is null)
             then 1                        
             when (q = '(' or q = '[')
              and (prior q = '(' or prior q = '[')
             then 1                          
             when (q = '(' or q = '[')
              and (prior q = ']' or prior q = ')')
             then 0                           
             when (q = ')' or q = ']') 
              and (prior q = '(' or prior q = '[')
             then 0                     
             when (q = ')' or q = ']') 
              and (prior q = ')' or prior q = ']')
             then -1
              end)
       over (partition by connect_by_root(curr_pos) 
                 order by connect_by_root(curr_pos), curr_pos) bracket_level              
   from all_brackets
connect by prior curr_pos = prev_pos
  start with q = '(' or q = '['),  
brackets_comb_status as        
(select start_pos,
        end_pos, 
        str,
        case 
         when q = ')' 
          and lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos) = '('
         then 'ok'
         when q = '(' 
          and lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos) = ')'
         then 'ok'
         when q = '(' 
          and lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos ) = ']'
         then 'ok'          
         when q = ']' 
          and lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos) = '['
         then 'ok'
         when q = '[' 
          and lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos) = ']'
         then 'ok'
         when q = '[' 
          and lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos) = ')'
         then 'ok'
         when lag(q) over (partition by start_pos, bracket_level 
                               order by start_pos, bracket_level, end_pos) is null
          and bracket_level > 0
         then 'ok'
         else 'not ok!'
        end status    
  from brackets_comb bc)
select str       "последовательность", 
       cnt       "колличество [", 
       start_pos "позиция с", 
       end_pos   "позиция по"
  from ( 
     select str,
            start_pos,
            end_pos,
            length(regexp_replace(str,'[)(]',''))/2 cnt,
            max(length(regexp_replace(str,'[)(]',''))/2) over (order by null) best_cnt            
       from ( 
        select str,
               start_pos,
               end_pos,         
               nvl(
                 lag(
                   case 
                     when status = 'ok' then null
                     else status
                   end
                   ignore nulls)
                  over (partition by start_pos order by start_pos, end_pos),
                 status
                ) status         
          from brackets_comb_status        
          )
      where status = 'ok' 
        and length(replace(str,'[','')) = length(replace(str,']',''))
        and length(replace(str,'(','')) = length(replace(str,')',''))     
    ) result
 where best_cnt = cnt

Автор: beIive

Поделиться

* - обязательные к заполнению поля