Реализация интервально-ассоциативного массива в СУБД Caché

от автора

Пост написан на основе статьи на хабре: Интервально-ассоциативный массив.

Поскольку изначальная реализация основана на слайсах (срезах) питона, нелишней для прочтения будет статья: Всё, что Вы хотели знать о слайсах. И, конечно, немного теории: Дерево Интервалов (Отрезков).
Итак, как же слайсы будут выглядеть в Caché?

В целом всё (почти) как в питоне.

Легко назначить:

i=##class(test.intervalmap).%New()
i.%("08:00","12:00","Иванов")
i.%("12:00","16:00","Петров")
Как узнать кто дежурил в 13:51 ?

i.%Get("13:51"),!
Петров
Легко просмотреть поэлементно полный список (для тех, кто привык мыслить "глобально"):

%=i.% zw %
%("08:00","12:00")="Иванов"
%("12:00","16:00")="Петров"

… или одной строкой:

i.Display(),!
[08:00, 12:00] => Иванов, [12:00, 16:00] => Петров
Удаление как по частям:

i.%("15:00","16:00")
i.Display(),!
[08:00, 12:00] => Иванов, [12:00, 15:00] => Петров
… так и целиком:

i.%("12:00","16:00")
i.Display(),!
[08:00, 12:00] => Иванов
Перекрывание ключей должно обрабатываться корректно:

i.%("11:00","15:00","Сидоров")
i.Display(),!
[08:00, 11:00] => Иванов, [11:00, 15:00] => Сидоров
Cоседние ключи с одинаковыми значениями должны склеиваться автоматически. Например, если вы назначили Сидорову подежурить так же с 15 до 17, то навряд ли это должно быть две смены подряд, скорее — одна более длинная:

i.%("15:00","17:00","Сидоров")
i.Display(),!
[08:00, 11:00] => Иванов, [11:00, 17:00] => Сидоров
Добавим пару записей:

i.%("17:00","20:00","Петров")
i.%("21:00","23:00","Сидоров")
i.Display(),!
[08:00, 11:00] => Иванов, [11:00, 17:00] => Сидоров, [17:00, 20:00] => Петров, [21:00, 23:00] => Сидоров
Часто возникает задача урезать расписание, оставив из нескольких идущих подряд элементов только последние. Например, нужно узнать, кто закрывал рабочий день:

i.Shrink()
i.Display(),!
[08:00, 20:00] => Петров, [21:00, 23:00] => Сидоров
Этот же метод можно использовать для проверки полностью ли ваше расписание охватывает рабочий день.

Дополнение

  • Была учтена небольшая ошибка в исходном коде по склейке соседних ключей, а именно:

    Питон:

    >>> timetable = intervalmap() >>> timetable[:] = 'Иванов' >>> timetable['11:00':'13:00'] = 'Иванов' >>> print timetable {[None, '13:00'] => 'Иванов', ['13:00', None] => 'Иванов'}

    COS:

    i=##class(test.intervalmap).%New()
    i.%(,,"Иванов")
    i.%("11:00","13:00","Иванов")
    i.Display(),!
    [None, None] => Иванов

  • Одинаковые ключи в паре, как в исходном коде, здесь не допускаются, но вы для себя можете включить их обратно.
    Конечно же в качестве ключей могут выступать любые числовые/строковые значения. Единственное, нужно следить, чтобы все они в рамках одного массива (объекта) были однотипны.
    Например:

    i=..%New()
    i.%(9,,"!")
    i.%(,5,"Hello")
    i.%(6,7,"World")
    i.Display(),!
    [None, 5] => Hello, [6, 7] => World, [9, None] => !

    i.Reset()
    i.%(,$zdh("24.10.2005"),"A")
    i.%($zdh("11.11.2005"),$zdh("17.11.2005"),"B")
    i.%($zdh("30.11.2005"),,"C")
    i.Display(),!
    [None, 60197] => A, [60215, 60221] => B, [60234, None] => C
    И напоследок ещё один пример посложнее:

    i.Reset()
    i.%(,,$c(8734))
    i.%(10,11,"Иванов")
    i.%(12,13,"Иванов")
    i.%(14,16,"Петров")
    i.%(11,15,"Иванов")
    i.%(8,12,"Сидоров")
    i.%(20,21)
    i.%(22,,"Сидоров")
    i.Display(),!

    Результат под спойлером

    [None, 8] => ∞, [8, 12] => Сидоров, [12, 15] => Иванов, [15, 16] => Петров, [16, 20] => ∞, [21, 22] => ∞, [22, None] => Сидоров

    Больше примеров вы найдёте в исходном коде.

    Код класса

    Class test.intervalmap Extends %RegisteredObject Final ]
    {
    Parameter None [ FinalInternal ] = -1;
    Property bounds As %List InternalPrivateReadOnlyServerOnly = 1, Transient ];
    Property items As %List InternalPrivateReadOnlyServerOnly = 1, Transient ];
    Property upperItem [ InitialExpression = {..#None}, InternalPrivateReadOnlyServerOnly = 1, Transient ];
    Property % [ MultiDimensionalReadOnlyServerOnly = 1, Transient ];
    ClassMethod Slice(list As %Liststartendvalue As %ListAs %List InternalPrivate ]
    {
        
    if start=end {
            
    if start="" {
                
    list=""
            
    }elseif start=1 {
                
    list=value_list
            
    }else{
                
    if start>$ll(list{
                    
    list=list_value
                
    }else{
                    
    s:start<$ll(liststart=start+1
                    
    s $li(list,start,start)=value_$lb($li(list,start))
                
    }
            }
        }
    else{
            
    if end="" {
                
    s:start>$ll(liststart=$ll(list)
                
    s $li(list,start,*+1)=value
            
    }else{
                
    start=$g(start,1)
                
    if end=1 {
                    
    list=..Slice(list,start,end,value)
                
    }else{
                    
    s $li(list,start,end-1)=value
                
    }
            }
        }
        
    list
    }
    Method Reset()
    {
        
    i%%
        
    (i%bounds,i%items)="",i%upperItem=..#None
    }
    Method %(start = {$g(start,..#None)}, stop = {$g(stop,..#None)}, value = {$g(value,..#None)}) As %Status
    {
        
    q🙁start>=stop)&((start‘=..#None)&(stop‘=..#None)) $$$OK
        
        s 
    startPoint=$s(start=..#None:0,1:..bisectLeft(start))
        
    endPoint=$s(stop=..#None:0,1:..bisectLeft(stop))
        
        
    if startPoint>=1 {
            
    s🙁startPoint <= $ll(..bounds))&&($li(..bounds,startPoint)<startstartPoint startPoint + 1
          
    s🙁endPoint >= 1)&&(endPoint <= $ll(..bounds))&&($li(..bounds,endPoint)<=stopendPoint endPoint + 1
          
        
    if endPoint>=1 {
            
    i%bounds=..Slice(i%bounds,startPoint,endPoint,$lb(start,stop))
            
    i%items=..Slice(i%items,startPoint,endPoint,$s(startPoint <= $ll(..items):$lb($li(..items,startPoint),value),1:$lb(..upperItem,value)))
        
    }else{
          
    s $li(i%bounds,startPoint,*+1) = $lb(start)
          
    s $li(i%items,startPoint,*+1)=$s(startPoint <= $ll(..items):$lb($li(..items,startPoint),value),1:$lb(..upperItem))
          
    i%upperItem = value
        
    }
        }
    else{
        
    if endPoint>=1 {
            
    i%bounds=..Slice(i%bounds,1,endPoint,$lb(stop))
            
    i%items=..Slice(i%items,1,endPoint,$lb(value))
        
    }else{
          
    (i%bounds,i%items) = ""
          
    i%upperItem = value
        
    }
        }
        
    i=1
        
    while (i<=($ll(..items)-1))
        
    {
          
    i $li(..items,i)=$li(..items,i+1) {
            
    s $li(i%items,i,i)=""
            
    s $li(i%bounds,i,i)=""
          
    }else {
              
    i=i+1
          
    }
      }
      
    s🙁$ll(..items)=1)&&($li(i%items,1)=i%upperItem) (i%items,i%bounds)=""
              
      
    ..repr()
      
      
    q $$$OK
    }
    Method %Get(xAs %String ServerOnly = 1 ]
    {
        
    index=..bisectRight(x)
        
    r=$s(index<=$ll(i%items):$li(i%items,index),1:i%upperItem)
        
    q $s(r=..#None:"",1:r)
    }
    Method bisectLeft(xAs %String InternalPrivateServerOnly = 1 ]
    {
        
    lo = 1
        
    hi $ll(i%bounds)+1
        
    while (lo hi{
        
    mid = (lo+hi)\2
        
    if $li(i%bounds,mid) < {
            
    lo mid+1
        
    else {
            
    hi mid
        
    }
        }
        
    lo
    }
    Method bisectRight(xAs %String InternalPrivateServerOnly = 1 ]
    {
        
    lo = 1
        
    hi $ll(i%bounds)+1
        
    while (lo hi{
        
    mid = (lo+hi)\2
        
    if $li(i%bounds,mid{
            
    hi mid
        
    else {
            
    lo mid+1
        
    }
        }
        
    lo
    }
    Method repr() [ InternalPrivateServerOnly = 1 ]
    {
      
    i%%
      
    previousBound=..#None
      f 
    i=1:1:$ll(..bounds{
          
    b=$li(..bounds,i)
          
    v=$li(..items,i)
          
    s:v‘=..#None i%%(previousBound,b)=v
          
    previousBound=b
      
    }
      
    s:..upperItem‘=..#None i%%(previousBound,..#None)=..upperItem
    }
    Method Shrink()
    {
        
    i=1
        
    while (i<=($ll(..items)-1))
        
    {
          
    i $li(..items,i)’=..#None,$li(..items,i+1)’=..#None {
            
    s $li(i%items,i,i)=""
            
    s $li(i%bounds,i,i)=""
          
    }else {
              
    i=i+1
          
    }
      }
      
    ..repr()
    }
    Method Display() As %String
    {
        
    #define IsNone(%s) $s(%s=..#None:"None",1:%s)
        
        s 
    key=$q(i%%,1,v),s=""
        
    while (key‘=""{
            
    s=s_$lb($$$FormatText("[%1, %2] => %3",$$$IsNone($qs(key,1)),$$$IsNone($qs(key,2)),v))
            
    key $q(@key,1,v)
        
    }
        
    q $lts(s,", ")
    }
    /// <example>d ##class(test.intervalmap).Test1()</example>
    ClassMethod 
    Test1() [ InternalServerOnly = 1 ]
    {
        
    %
        
    old=$system.Process.Undefined(2)
        
        
    try{
            
    i=..%New()
            
    i.%("08:00","12:00","Иванов")
            
    i.%("12:00","16:00","Петров")
            
    i.%("15:00","16:00")
            
    i.%("12:00","16:00")
            
    i.%("11:00","15:00","Сидоров")
            
    i.%("15:00","17:00","Сидоров")
            
    i.%("17:00","20:00","Петров")
            
    i.%("21:00","23:00","Сидоров")
            
    i.Display(),!
            
    "[13:51] = ",i.%Get("13:51"),!
            
    ;k % m %=i.% zw %
            
    i.Shrink()
            
    i.Display(),!
        
    }catch(ex){
            
    #dim ex As %Exception.AbstractException
            
    "Error = ",ex.DisplayString(),!
        
    }
        
    d $system.Process.Undefined(old)
    }
    /// <example>d ##class(test.intervalmap).Test2()</example>
    ClassMethod 
    Test2() [ InternalServerOnly = 1 ]
    {
        
    #define Assert(%i,%s) if %i.Display()’=%s {$$$ThrowStatus($$$ERROR($$$GeneralError,%s))} else {w %i.Display(),!}
        #define 
    AssertGet(%i,%t,%s) if %i.%Get(%t)’=%s {$$$ThrowStatus($$$ERROR($$$GeneralError,%s))} else {w "(%t) = ",%i.%Get(%t),!}
        s 
    old=$system.Process.Undefined(2)
        
        
    try{
            
            
    i=..%New()
            
    i.%(0,5,"0-5")
            
    i.%(8,12,"8-12")
            
    $$$AssertGet(i,2,"0-5")
            
    $$$AssertGet(i,10,"8-12")
            
    $$$AssertGet(i,-1,"")
            
    $$$AssertGet(i,17,"")
            
            
    i.%(4,9,"4-9")
            
    $$$Assert(i,"[0, 4] => 0-5, [4, 9] => 4-9, [9, 12] => 8-12")
            
    i.%(,0,"less than 0")
            
    $$$AssertGet(i,-5,"less than 0")
            
    $$$AssertGet(i,0,"0-5")
            
    $$$Assert(i,"[None, 0] => less than 0, [0, 4] => 0-5, [4, 9] => 4-9, [9, 12] => 8-12")
            
            
    i.%(21,,"more than twenty")
            
    $$$AssertGet(i,42,"more than twenty")
            
    i.%(10.5,15.5,"10.5-15.5")
            
    $$$AssertGet(i,11.5,"10.5-15.5")
            
    $$$AssertGet(i,0.5,"0-5")
            
    $$$Assert(i,"[None, 0] => less than 0, [0, 4] => 0-5, [4, 9] => 4-9, [9, 10.5] => 8-12, [10.5, 15.5] => 10.5-15.5, [21, None] => more than twenty")
            
            
    i.Reset()
            
            
    i.%(0,2,1)
            
    i.%(2,8,2)
            
    i.%(4,,3)
            
    i.%(5,6,4)
            
    $$$Assert(i,"[0, 2] => 1, [2, 4] => 2, [4, 5] => 3, [5, 6] => 4, [6, None] => 3")
            
        
    }catch(ex){
            
    #dim ex As %Exception.AbstractException
            
    "Error = ",ex.DisplayString(),!
        
    }
        
    d $system.Process.Undefined(old)
    }
    /// <example>d ##class(test.intervalmap).Test3()</example>
    ClassMethod 
    Test3() [ InternalServerOnly = 1 ]
    {
        
    #define Assert(%i,%s) if %i.Display()’=%s $$$ThrowStatus($$$ERROR($$$GeneralError,%s))
        
    #define AssertGet(%i,%t,%s) if %i.%Get(%t)’=%s $$$ThrowStatus($$$ERROR($$$GeneralError,%s))
        
    old=$system.Process.Undefined(2)
        
        
    try{
            
            
    i=..%New()
            
    i.%(9,,"!")
            
    $$$Assert(i,"[9, None] => !")
            
    i.%(,5,"Hello")
            
    i.%(6,7,"World")
            
    $$$Assert(i,"[None, 5] => Hello, [6, 7] => World, [9, None] => !")
            
    i.%(8,10,"(Test)")
            
    $$$Assert(i,"[None, 5] => Hello, [6, 7] => World, [8, 10] => (Test), [10, None] => !")
            
    i.%(,3,"My,")
            
    $$$Assert(i,"[None, 3] => My,, [3, 5] => Hello, [6, 7] => World, [8, 10] => (Test), [10, None] => !")
            
    i.%(5.5,6,"Cruel")
            
    $$$Assert(i,"[None, 3] => My,, [3, 5] => Hello, [5.5, 6] => Cruel, [6, 7] => World, [8, 10] => (Test), [10, None] => !")
            
    i.%(6,6.5,"And Harsh")
            
    $$$Assert(i,"[None, 3] => My,, [3, 5] => Hello, [5.5, 6] => Cruel, [6, 6.5] => And Harsh, [6.5, 7] => World, [8, 10] => (Test), [10, None] => !")
            
    i.%(5.9,6.6)
            
    $$$Assert(i,"[None, 3] => My,, [3, 5] => Hello, [5.5, 5.9] => Cruel, [6.6, 7] => World, [8, 10] => (Test), [10, None] => !")
            
    "Test 1 OK",!
            
            
    i.Reset()
            
    i.%(,0,"A")
            
    i.%(2,5,"B")
            
    i.%(8,10,"C")
            
    i.%(12,,"D")
            
    $$$Assert(i,"[None, 0] => A, [2, 5] => B, [8, 10] => C, [12, None] => D")
            
    i.%(,,"K")
            
    $$$Assert(i,"[None, None] => K")
            
    $$$AssertGet(i,5,"K")
            
    i.%(0,10,"L")
            
    i.%(6,8,"M")
            
    i.%(20,,"J")
            
    $$$AssertGet(i,-1,"K")
            
    $$$AssertGet(i,5,"L")
            
    $$$AssertGet(i,7,"M")
            
    $$$AssertGet(i,9,"L")
            
    $$$AssertGet(i,15,"K")
            
    "Test 2 OK",!
            
            
    i.Reset()
            
    i.%(,$zdh("24.10.2005"),"A")
            
    i.%($zdh("11.11.2005"),$zdh("17.11.2005"),"B")
            
    i.%($zdh("30.11.2005"),,"C")
            
    $$$AssertGet(i,$zdh("25.09.2005"),"A")
            
    $$$AssertGet(i,$zdh("23.10.2005"),"A")
            
    $$$AssertGet(i,$zdh("26.10.2005"),"")
            
    $$$AssertGet(i,$zdh("09.11.2005"),"")
            
    $$$AssertGet(i,$zdh("16.11.2005"),"B")
            
    $$$AssertGet(i,$zdh("23.11.2005"),"")
            
    $$$AssertGet(i,$zdh("29.11.2005"),"")
            
    $$$AssertGet(i,$zdh("30.11.2005"),"C")
            
    $$$AssertGet(i,$zdh("03.12.2005"),"C")
            
    "Test 3 OK",!
            
        
    }catch(ex){
            
    #dim ex As %Exception.AbstractException
            
    "Error = ",ex.DisplayString(),!
        
    }
        
    d $system.Process.Undefined(old)
    }
    }

    Или скачать класс test.intervalmap.
    Код тестировался на версии Caché 2015.1, но переделать класс для предыдущих версий не составит особого труда.

ссылка на оригинал статьи http://habrahabr.ru/post/256455/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *