вторник, 16 мая 2017 г.

Комбинаторика в СУБД Oracle 11g

По запросу "oracle factorial" с ходу нагугливаются два подхода к вычислению факториала в СУБД Oracle:

  • самоочевидный: в цикле получить произведение натуральных чисел от 1 до n

    create or replace
    function fac1(n pls_integer) return number
    is
        f number := 1;
    begin
        if n in (0, 1) then
            return 1;
        end if;
        for i in 2..n loop
            f := f * i;
        end loop;
        return f;
    end fac1;
    /
    
  • забавный: вместо произведения натуральных чисел от 1 до n найти сумму их логарифмов, а затем возвести основание логарифма в найденную степень

    create or replace 
    function fac2(n pls_integer) return number
    is
        f number;
    begin
        select round(exp(sum(ln(level))))
        into f
        from dual
        connect by level <= n;
        return f;
    end fac2;
    /
    

Обе функции дают одинаковые результаты, пока значение аргумента невелико:

SQL> select level-1, fac1(level-1), fac2(level-1) from dual connect by level <= 11;

   LEVEL-1 FAC1(LEVEL-1) FAC2(LEVEL-1)
---------- ------------- -------------
         0             1             1
         1             1             1
         2             2             2
         3             6             6
         4            24            24
         5           120           120
         6           720           720
         7          5040          5040
         8         40320         40320
         9        362880        362880
        10       3628800       3628800
11 rows selected

Начиная с n = 33 возникают расхождения (из-за ограниченной точности нецелочисленных вычислений):

SQL> column fac2 format 999999999999999999999999999999999999999999999
SQL> column fac1 format 999999999999999999999999999999999999999999999

SQL> select lvl, fac1(lvl) fac1, fac2(lvl) fac2
  2  from (select level-1 lvl from dual connect by level <= 36)
  3  where fac1(lvl) != fac2(lvl);

       LVL                                           FAC1                                           FAC2
---------- ---------------------------------------------- ----------------------------------------------
        33          8683317618811886495518194401280000000          8683317618811886495518194401280000001
        34        295232799039604140847618609643520000000        295232799039604140847618609643520000022
        35      10333147966386144929666651337523200000000      10333147966386144929666651337523200000800

Далее, обе функции перестают возвращать результат на 9-том десятке:

SQL> select level+80, fac1(level+80) fac from dual connect by level <= 10;

  LEVEL+80        FAC
---------- ----------
        81 5.797E+120
        82 4.754E+122
        83 3.946E+124
        84          ~
        85          ~
        86          ~
        87          ~
        88          ~
        89          ~
        90          ~

10 rows selected.

SQL> select level+80, fac2(level+80) fac from dual connect by level <= 10;

ORA-01426: numeric overflow
ORA-06512: at "AY.FAC2", line 6

Как известно, значение типа number в Oracle не может быть больше 10126. А значение 84! оказывается больше этого числа.

Принимая во внимание этот факт, предлагаю третий подход к получению факториала.

Пока мы работаем в СУБД Oracle c типом данных number, нас не интересуют факториалы чисел больше 83. Поэтому, сохраним однажды вычисленные факториалы чисел от 0 до 83 в таблице PL/SQL и будем получать значение факториала n из нее по индексу n+1:

create or replace type number_nt as table of number;
/

create or replace 
function fac3(n pls_integer) return number
is
    c_fac number_nt := number_nt(
        1, --0
        1, --1
        2, --2
        6, --3
        24, --4
        120, --5
        720, --6
        5040, --7
        40320, --8
        362880, --9
        3628800, --10
        39916800, --11
        479001600, --12
        6227020800, --13
        87178291200, --14
        1307674368000, --15
        20922789888000, --16
        355687428096000, --17
        6402373705728000, --18
        121645100408832000, --19
        2432902008176640000, --20
        51090942171709440000, --21
        1124000727777607680000, --22
        25852016738884976640000, --23
        620448401733239439360000, --24
        15511210043330985984000000, --25
        403291461126605635584000000, --26
        10888869450418352160768000000, --27
        304888344611713860501504000000, --28
        8841761993739701954543616000000, --29
        265252859812191058636308480000000, --30
        8222838654177922817725562880000000, --31
        263130836933693530167218012160000000, --32
        8683317618811886495518194401280000000, --33
        295232799039604140847618609643520000000, --34
        10333147966386144929666651337523200000000, --35
        371993326789901217467999448150835200000000, --36
        13763753091226345046315979581580902400000000, --37
        523022617466601111760007224100074291200000000, --38
        20397882081197443358640281739902897356800000000, --39
        815915283247897734345611269596115894272000000000, --40
        33452526613163807108170062053440751665150000000000, --41
        1405006117752879898543142606244511569936000000000000, --42
        60415263063373835637355132068513997507200000000000000, --43
        2658271574788448768043625811014615890320000000000000000, --44
        119622220865480194561963161495657715064000000000000000000, --45
        5502622159812088949850305428800254892944000000000000000000, --46
        258623241511168180642964355153611979968400000000000000000000, --47
        12413915592536072670862289047373375038480000000000000000000000, --48
        608281864034267560872252163321295376886000000000000000000000000, --49
        30414093201713378043612608166064768844300000000000000000000000000, --50
        1551118753287382280224243016469303211060000000000000000000000000000, --51
        80658175170943878571660636856403766975120000000000000000000000000000, --52
        4274883284060025564298013753389399649681000000000000000000000000000000, --53
        230843697339241380472092742683027581082800000000000000000000000000000000, --54
        12696403353658275925965100847566516959550000000000000000000000000000000000, --55
        710998587804863451854045647463724949735000000000000000000000000000000000000, --56
        40526919504877216755680601905432322134900000000000000000000000000000000000000, --57
        2350561331282878571829474910515074683820000000000000000000000000000000000000000, --58
        138683118545689835737939019720389406345000000000000000000000000000000000000000000, --59
        8320987112741390144276341183223364380700000000000000000000000000000000000000000000, --60
        507580213877224798800856812176625227222700000000000000000000000000000000000000000000, --61
        31469973260387937525653122354950764087810000000000000000000000000000000000000000000000, --62
        1982608315404440064116146708361898137532000000000000000000000000000000000000000000000000, --63
        126886932185884164103433389335161480802000000000000000000000000000000000000000000000000000, --64
        8247650592082470666723170306785496252130000000000000000000000000000000000000000000000000000, --65
        544344939077443064003729240247842752641000000000000000000000000000000000000000000000000000000, --66
        36471110918188685288249859096605464426900000000000000000000000000000000000000000000000000000000, --67
        2480035542436830599600990418569171581030000000000000000000000000000000000000000000000000000000000, --68
        171122452428141311372468338881272839091000000000000000000000000000000000000000000000000000000000000, --69
        1.197857166996989179607278372168909873640000000000000000000000000000000000000000000000000000000E+100, --70
        8.504785885678623175211676442399260102844000000000000000000000000000000000000000000000000000000E+101, --71
        6.123445837688608686152407038527467274048000000000000000000000000000000000000000000000000000000E+103, --72
        4.470115461512684340891257138125051110055000000000000000000000000000000000000000000000000000000E+105, --73
        3.307885441519386412259530282212537821441000000000000000000000000000000000000000000000000000000E+107, --74
        2.480914081139539809194647711659403366081000000000000000000000000000000000000000000000000000000E+109, --75
        1.885494701666050254987932260861146558222000000000000000000000000000000000000000000000000000000E+111, --76
        1.451830920282858696340707840863082849831000000000000000000000000000000000000000000000000000000E+113, --77
        1.132428117820629783145752115873204622868000000000000000000000000000000000000000000000000000000E+115, --78
        8.946182130782975286851441715398316520660000000000000000000000000000000000000000000000000000000E+116, --79
        7.156945704626380229481153372318653216530000000000000000000000000000000000000000000000000000000E+118, --80
        5.797126020747367985879734231578109105390000000000000000000000000000000000000000000000000000000E+120, --81
        4.753643337012841748421382069894049466420000000000000000000000000000000000000000000000000000000E+122, --82
        3.945523969720658651189747118012061057130000000000000000000000000000000000000000000000000000000E+124  --83
    );
begin
    return c_fac(n+1);
end fac;
/

Проверим работу новой функции:

SQL> select level-1, fac1(level-1), fac2(level-1), fac3(level-1) from dual connect by level <= 21;

   LEVEL-1 FAC1(LEVEL-1) FAC2(LEVEL-1) FAC3(LEVEL-1)
---------- ------------- ------------- -------------
         0             1             1             1
         1             1             1             1
         2             2             2             2
         3             6             6             6
         4            24            24            24
         5           120           120           120
         6           720           720           720
         7          5040          5040          5040
         8         40320         40320         40320
         9        362880        362880        362880
        10       3628800       3628800       3628800
11 rows selected

SQL> select lvl
  2  from (select level-1 lvl from dual connect by level <= 84)
  3  where fac3(lvl) != fac1(lvl)
  4  ;

       LVL
----------

Последний запрос показывает, что на диапазоне входных значений от 0 до 83 результаты функций fac1 и fac3 совпадают.

Ниже привожу текст PL/SQL пакета, реализующего основные функции комбинаторики с использованием таблицы факториалов. Пакет предоставляет следующие функции, возвращающие результат типа number:

ФункцияОписание
fac(n pls_integer)факториал n
permutations(n pls_integer)число перестановок n элементов
permutations_with_rep(n pls_integer, k number_nt)число перестановок n элементов c повторениями, где таблица k задает кол-во повторений
allocations(n pls_integer, k pls_integer)число размещений k элементов из n
allocations_with_rep(n pls_integer, k pls_integer)число размещений с повторениями k элементов из n
combinations(n pls_integer, k pls_integer)число сочетаний k элементов из n
combinations_with_rep(n pls_integer, k pls_integer)число сочетаний с повторениями k элементов из n
n_choose_k(n pls_integer, k pls_integer)то же, что combinations
create or replace package comics is
/*******************************************************************************
    Common com(binator)ics functions.

Changelog
    2017-05-12 Andrei Trofimov Create package.

********************************************************************************
Copyright (C) 2017 by Andrei Trofimov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

********************************************************************************
*/

    -- n!
    function fac(n pls_integer) return number;

    -- n!
    function permutations(n pls_integer) return number;

    -- n! / (k1!*k2!*...*km!), where sum(k) <= n
    function permutations_with_rep(n pls_integer, k number_nt) return number;

    -- n! / (n-k)!
    function allocations(n pls_integer, k pls_integer) return number;
    
    -- n to the k'th power
    function allocations_with_rep(n pls_integer, k pls_integer) return number;

    -- n! / ((n-k)! * k!)
    function combinations(n pls_integer, k pls_integer) return number;
    
    -- (k+n-1)! / ((n-1)! * k!)
    function combinations_with_rep(n pls_integer, k pls_integer) return number;
    
    -- another name for combinations
    function n_choose_k(n pls_integer, k pls_integer) return number;
    
end comics;
/

create or replace package body comics is

    /*
    -- make list of factorials 0 through 83
    declare
        r number := 1;
    begin
        evu.p(r||', --0');
        for i in 1..83 loop
            r := r * i;
            evu.p(r||', --'||i);
        end loop;
    end;
    */
    c_fac number_nt := number_nt(
        1, --0
        1, --1
        2, --2
        6, --3
        24, --4
        120, --5
        720, --6
        5040, --7
        40320, --8
        362880, --9
        3628800, --10
        39916800, --11
        479001600, --12
        6227020800, --13
        87178291200, --14
        1307674368000, --15
        20922789888000, --16
        355687428096000, --17
        6402373705728000, --18
        121645100408832000, --19
        2432902008176640000, --20
        51090942171709440000, --21
        1124000727777607680000, --22
        25852016738884976640000, --23
        620448401733239439360000, --24
        15511210043330985984000000, --25
        403291461126605635584000000, --26
        10888869450418352160768000000, --27
        304888344611713860501504000000, --28
        8841761993739701954543616000000, --29
        265252859812191058636308480000000, --30
        8222838654177922817725562880000000, --31
        263130836933693530167218012160000000, --32
        8683317618811886495518194401280000000, --33
        295232799039604140847618609643520000000, --34
        10333147966386144929666651337523200000000, --35
        371993326789901217467999448150835200000000, --36
        13763753091226345046315979581580902400000000, --37
        523022617466601111760007224100074291200000000, --38
        20397882081197443358640281739902897356800000000, --39
        815915283247897734345611269596115894272000000000, --40
        33452526613163807108170062053440751665150000000000, --41
        1405006117752879898543142606244511569936000000000000, --42
        60415263063373835637355132068513997507200000000000000, --43
        2658271574788448768043625811014615890320000000000000000, --44
        119622220865480194561963161495657715064000000000000000000, --45
        5502622159812088949850305428800254892944000000000000000000, --46
        258623241511168180642964355153611979968400000000000000000000, --47
        12413915592536072670862289047373375038480000000000000000000000, --48
        608281864034267560872252163321295376886000000000000000000000000, --49
        30414093201713378043612608166064768844300000000000000000000000000, --50
        1551118753287382280224243016469303211060000000000000000000000000000, --51
        80658175170943878571660636856403766975120000000000000000000000000000, --52
        4274883284060025564298013753389399649681000000000000000000000000000000, --53
        230843697339241380472092742683027581082800000000000000000000000000000000, --54
        12696403353658275925965100847566516959550000000000000000000000000000000000, --55
        710998587804863451854045647463724949735000000000000000000000000000000000000, --56
        40526919504877216755680601905432322134900000000000000000000000000000000000000, --57
        2350561331282878571829474910515074683820000000000000000000000000000000000000000, --58
        138683118545689835737939019720389406345000000000000000000000000000000000000000000, --59
        8320987112741390144276341183223364380700000000000000000000000000000000000000000000, --60
        507580213877224798800856812176625227222700000000000000000000000000000000000000000000, --61
        31469973260387937525653122354950764087810000000000000000000000000000000000000000000000, --62
        1982608315404440064116146708361898137532000000000000000000000000000000000000000000000000, --63
        126886932185884164103433389335161480802000000000000000000000000000000000000000000000000000, --64
        8247650592082470666723170306785496252130000000000000000000000000000000000000000000000000000, --65
        544344939077443064003729240247842752641000000000000000000000000000000000000000000000000000000, --66
        36471110918188685288249859096605464426900000000000000000000000000000000000000000000000000000000, --67
        2480035542436830599600990418569171581030000000000000000000000000000000000000000000000000000000000, --68
        171122452428141311372468338881272839091000000000000000000000000000000000000000000000000000000000000, --69
        1.197857166996989179607278372168909873640000000000000000000000000000000000000000000000000000000E+100, --70
        8.504785885678623175211676442399260102844000000000000000000000000000000000000000000000000000000E+101, --71
        6.123445837688608686152407038527467274048000000000000000000000000000000000000000000000000000000E+103, --72
        4.470115461512684340891257138125051110055000000000000000000000000000000000000000000000000000000E+105, --73
        3.307885441519386412259530282212537821441000000000000000000000000000000000000000000000000000000E+107, --74
        2.480914081139539809194647711659403366081000000000000000000000000000000000000000000000000000000E+109, --75
        1.885494701666050254987932260861146558222000000000000000000000000000000000000000000000000000000E+111, --76
        1.451830920282858696340707840863082849831000000000000000000000000000000000000000000000000000000E+113, --77
        1.132428117820629783145752115873204622868000000000000000000000000000000000000000000000000000000E+115, --78
        8.946182130782975286851441715398316520660000000000000000000000000000000000000000000000000000000E+116, --79
        7.156945704626380229481153372318653216530000000000000000000000000000000000000000000000000000000E+118, --80
        5.797126020747367985879734231578109105390000000000000000000000000000000000000000000000000000000E+120, --81
        4.753643337012841748421382069894049466420000000000000000000000000000000000000000000000000000000E+122, --82
        3.945523969720658651189747118012061057130000000000000000000000000000000000000000000000000000000E+124  --83
    );
    
    procedure assert(condition boolean, message varchar2)
    is
    begin
        if not condition or condition is null then
            raise_application_error(-20001, 'Assertion error: '||message);
        end if;
    end assert;
    
    -- n!
    function fac(n pls_integer) return number
    is
    begin
        assert(n between 0 and 83, '0 <= n <= 83');
        return c_fac(n+1);
    end fac;
    
    -- n!
    function permutations(n pls_integer) return number
    is
    begin
        return fac(n);
    end permutations;
    
    -- n! / (k1!*k2!*...*km!), where sum(k) <= n
    function permutations_with_rep(n pls_integer, k number_nt) return number
    is
        l_sum pls_integer := 0;
        l_res pls_integer;
    begin
        l_res := fac(n);
        for i in 1..k.count loop
            l_sum := l_sum + k(i);
            assert(l_sum <= n, 'sum(k) <= n');
            l_res := l_res/fac(k(i));
        end loop;
        return l_res;
    end permutations_with_rep;
    
    -- n! / (n-k)!
    function allocations(n pls_integer, k pls_integer) return number
    is
    begin
        return fac(n)/fac(n-k);
    end allocations;
    
    -- n to the k'th power
    function allocations_with_rep(n pls_integer, k pls_integer) return number
    is
    begin
        return power(n, k);
    end allocations_with_rep;

    -- n! / ((n-k)! * k!)
    function combinations(n pls_integer, k pls_integer) return number
    is
    begin
        return fac(n)/fac(n-k)/fac(k);
    end combinations;
    
    -- (k+n-1)! / ((n-1)! * k!)
    function combinations_with_rep(n pls_integer, k pls_integer) return number
    is
    begin
        return fac(k+n-1)/fac(n-1)/fac(k);
    end combinations_with_rep;
    
    -- another name for combinations
    function n_choose_k(n pls_integer, k pls_integer) return number
    is
    begin
        return combinations(n,k);
    end n_choose_k;

end comics;
/

Я добавил проверку входных значений для функций пакета при помощи процедуры assert. Вот что будет, если ввести недопустимые значения:

SQL> select comics.fac(100) from dual;

ORA-20001: Assertion error: 0 <= n <= 83
ORA-06512: at "AY.ASSERT", line 5
ORA-06512: at "AY.COMICS", line 106

SQL> select comics.permutations_with_rep(5, number_nt(2,3,1)) from dual;

ORA-20001: Assertion error: sum(k) <= n
ORA-06512: at "AY.ASSERT", line 5
ORA-06512: at "AY.COMICS", line 126

Решим несколько задачек с помощью пакета comics.

Сколькими способами можно расставить слова в предложении "Белеет парус одинокий"?

SQL> select comics.permutations(3) from dual;

COMICS.PERMUTATIONS(3)
----------------------
                     6

Сколько разных чисел можно получить перестановкой цифр в числе 123234?

SQL> select comics.permutations_with_rep(6, number_nt(1,2,2,1)) from dual;

COMICS.PERMUTATIONS_WITH_REP(6
------------------------------
                           180

Сколько разных последовательностей из 5 карт можно вытащить из колоды в 52 карты, вынимая карты и не возвращая их в колоду?

SQL> select comics.allocations(52, 5) from dual;

COMICS.ALLOCATIONS(52,5)
------------------------
               311875200

Сколько разных чисел можно записать, имея в своем распоряжении 5 десятичных разрядов? Иными словами, сколько существует разных десятичных чисел с разрядностью не больше пяти?

SQL> select comics.allocations_with_rep(10, 5) from dual;

COMICS.ALLOCATIONS_WITH_REP(10
------------------------------
                        100000

Сколько сочетаний по 7 можно получить из 49 элементов? (Это "Спортлото" 7 из 49-ти.)

SQL> select comics.combinations(49, 7) from dual;

COMICS.COMBINATIONS(49,7)
-------------------------
                 85900584

Сколькими способами можно составить букет из 5 роз, если у нас есть розы трех цветов?

SQL> select comics.combinations_with_rep(3, 5) from dual;

COMICS.COMBINATIONS_WITH_REP(3
------------------------------
                            21

В заключение, удаляю результаты экспериментов (но оставляю пакет comics):

SQL> drop function fac1;
Function dropped

SQL> drop function fac2;
Function dropped

SQL> drop function fac3;
Function dropped

Комментариев нет:

Отправить комментарий