Welcome to my CRAZINESS

Оптимизирующий компилятор правил транскрипции

Под компиляцией я подразумеваю перевод правил в древовидное представление. Выполнение правил осуществляет маленькая несложная программка.

О том как устроенно дерево

Дерево состоит из узлов двух видов. Из узлов терминальных и не терминальных. Не терминальный узел (или операция) содержит в себе название операции и ссылки на первый и второй операнд. Операндами нетерминального узла могут быть сами операции, а могут быть терминальные вершины. Терминальная вершина содержит в себе название операнда и аргумент операнда. Если в подстановке содержится образец слова, то образец хранится в общем словаре, а в дереве номер слова по словарю. Саму компиляцию я обсуждать не буду, программа вроде лабораторной 3 семестра 8 факультета.

       |
      [=]
     /   \
  [1]     [+]
         /   \
      [2]     [7]

Оптимизация правил

То что можно сделать, но я не делал (но может потом сделаю)

  1. Лобовой перебор всех выражений, с целью получить минимальный эквивалент выражения.

    У меня была раньше мысль, сделать программу, которая программирует на машине Маркова или Тьюринга. Просто задаешь зависимость между условием и ответом. А программа подбирается в помощью перебора всех возможных сочетаний команд.

  2. Оптимизация с учетом вероятности срабатывания операндов логического выражения.
  3. Оптимизация с учетом устройства соседних правил для одной и той же буквы
  4. Вместо нескольких правил для одной буквы, можно построить конечный автомат
  5. Матричная оптимизация

Первое что приходит в голову

Я всегда руководствуюсь правилом : "не делай то, что до тебя сделали". И разумеется первое что приходит в голову, утащить где-нибудь хорошую идею. Просто загружаешь Maple5 resealse 4 и пытаешься подглядеть как он это делает.

> with(logic); interface(verboseproc=3);

[bequal, bsimp, canon, convert/MOD2, convert/frominert,convert/toinert, distrib, dual, environ, randbool, satisfy, tautology]

> q:=(a &or b) &and (a &or c);

q := (a &or b) &and (a &or c)

> printlevel:=15; bsimp(q);


Maple поступает так : заменяет OR сложением, заменяет AND умножением, дает команду раскрыть скобки, удаляет степени. В итоге получается самый настоящий многочлен Жигалкина. И последним шагом, пытается с помощью перебора минимизировать запись.
&or := proc()
local a;
option `Copyright (c) 1992 by the University of Waterloo. All rights reserved.`;
    a := [args];
    if a = [] then 'procname()'
    elif nargs = 1 then a[1]
    else convert(a, `+`)
    fi
end

&and := proc()
local a;
option `Copyright (c) 1992 by the University of Waterloo. All rights reserved.`;
    a := [args];
    if a = [] then 'procname()' else convert(a, `*`) fi
end

Вывод простой Maple не упрощает логику. Он просто приводит выражение в Нормальную Дизьюнктивную Форму и немного ее уменьшает. Т.е. он упрощает хуже чем можно упрощать. А если подсунуть формулу q:=(a &and b) &or (a &and c); то с ней ничего не происходит, потому как она и так в дизьюктивной форме.

Наш выход

Я пытаюсь реализовать не самый крутой метод, но для наших правил он работает достаточно хорошо. Программа просто ходит по дереву и применяет набор фиксированных правил :

  1. раскрываем переопределенные правила
  2. раскрываем операции со множествами
  3. применяем во всех возможных местах набор упрощающих правил

Сначала о мелком

// вначале было слово, и слово это было два байта, а больше ничего не было

Зоной я называю отдельную часть дерева, элементы которой состоят из одинаковых операций.

Fuse у меня называется затычка или элемент который ничего не делает. Если необходимо убрать одну операцию из зоны, то вместо одного из аргументов операции ставится затычка. А оператор удаляет процедура сборки мусора. После каждой написанной программы меня преследует ощущение, что ее можно было написать короче чем она написана.

bool isSummation(char) возвращает истину если char='+' или '-'

bool isSimmentrion(char) истина если аргументы операции char можно менять местами

bool isSimilar(char, char) истина если операции похожи. Т.е. если они в прямом смысле похожи или один из них минус, а другой плюс. Короче, если их можно менять местами при сортировке.

bool isDualLogic(char, char) истина для противоположных логических операций

bool isLogic(char) истина если операция является логической.

bool isDualAtom(char, char) возвращает истину для противоположных логических операций. Противоположными считаются следующие операции

= #
~ $

bool isCycle(char) возвращает истину если указанная операция является циклом.

Список основных необходимых действий

void Destroy(tree) уничтожает дерево

void Duplicate(NoodOp*,NoodOp**) создает точную копию заданного дерева

void FuseTreeP2(NoodOp*) ставит затычку в ответвление P2 дерева.

      1
     /                          1
--[^]     3                    /
     \   /                --[^]
      [^]                      \
         \                      [Fuse]
          6

void HideTreeP1 HideTreeP2(NoodOp*) тоже самое, но само поддерево физически не уничтожается.

void ExpandSet(NoodOp*) избавляет дерево от операций между множествами, заменяя их логическими конструкциями. Возможно я буду это использовать, при вычислениях. Хотя проверяя принадлежность буквы пересечению множеств, я все равно не стал бы пересекать множества, а играл бы только с проверяемой буквой. Если точнее, оперируя набором из 16 множеств. Можно составить таблицу WORD[256], ячейки которой должны своими битами указывать какому множеству буква принадлежит, а какому нет. А сложное выражение (A1\A2)\(A3\A4)U(A6\A9) можно заменить двумя битовыми масками, которые говорят о том какому множеству символ должен принадлежать, а какому нет. И натыкаясь на подобное выражение, просто накладывать обе маски на элемент массива.

x ~ A1 U A2  =  x ~ A1 | x ~ A2
x ~ A1 @ A2  =  x ~ A1 & x ~ A2
x ~ A1 \ A2  =  x ~ A1 & x $ A2
x $ A1 U A2  =  x $ A1 & x $ A2
x $ A1 @ A2  =  x $ A1 | x $ A2
x $ A1 \ A2  =  x $ A1 | x ~ A2

void LinearTree(NoodOp*) выполняет разбалансировку дерева. Т.е. делает так, чтобы одна операция не осуществлялась между результатами себе подобных. Кроме того ставит в конце каждой зоны заглушку. Этот фокус упрощает многие операции над деревом. Не затрагиваются операции со множествами, и им подобные. При перераспределении операций '+', '-' из знак изменяется.

         x1                        x1                  [fuse]
        /                         /                   /
     [^]                       [^]                 [^]
    /   \                     /   \               /   \
   /     x2                [^]     x2          [^]     x1
[^]                       /   \               /   \
   \     x3            [^]     x4          [^]     x2
    \   /                 \               /   \
     [^]                   x3          [^]     x4
        \                                 \
         x4                                x3

    1                             1
   /                             /
[-]     2                     [+]
   \   /                     /   \
    [-]                   [-]     3
       \                     \
        3                     2

1-(2-3)=1-2+3               1-2+3

void OrderTree(NoodOp*) сортирует разбалансированое дерево. (не разбалансированое дерево будет приведено в хаос).

void CanonTree(NoodOp*) пытается привести два дерева к одинаковому виду.

void CanonTree(NoodOp *tree)
{
   LinearTree(tree);
   OrderTree(tree);
}

Таким образом деревья представляющие собой одинаковые формулы, приводятся к одинаковому виду, в случае если они не выражены по разному. Эти преобразования позволяют сравнивать два дерева, не прибегая к сложной процедуре сравнения.

void WipeTree(NoodOp**) обходит дерево и очищает его, от так называемых заглушек. Которые остаются после некоторых преобразований. Основная проблема в том, что после некоторых действий в программе возникают фальшивые зоны.

            /
         [^]
        /
     [^]     [Fuse]
    /   \   /
 [^]     [|]
/           \   /
             [^]
            /

char ifCoincedesTree(NoodOp*, NoodOp*) сравнивает два преобразованных дерева. Возвращает ноль, если они равны. И разницу между несовпадающими элементами в противном случае. Процедура ищет разницу между деревьями в следующем порядке : имя узла; затем если это операнд, то аргумент; если операция, то поддерево p2, затем p1.

bool ifContainsZone(NoodOp*, NoodOp*) возвращает истину если первая зона является частным случаем второй зоны. Если зоны не состоят из логических операций, то возвращает результат прямого сравнения.

                                 [Fuse]
                                /
         [Fuse]              [|]
        /                   /   \
     [|]                 [|]     X3
    /   \               /   \
-[|]     X3         -[|]     X2
    \                   \
     X1                  X1
   

bool ifDualCoincedesTree(NoodOp *tree1, NoodOp *tree2) сравнивает два дерева. Возвращает истину, если деревья являются, противоположными логическими операциями, над одинаковыми операндами.

     X1           X1
    /            /
-[=]         -[≠]
    \            \
     X2           X2
   

void ExludeZone(NoodOp*,NoodOp*, char new) находит и удаляет элементы первой зоны из второй зоны. Если второе дерево операция, то эта операция уничтожается без, какого либо анализа. new операнд которым нужно заместить уничтоженные элементы.

Первое           Второе                 Результат
      ?
     /            ?                    ?
?-[?]              \                    \
     \              [3]                  [Fuse]
      ?


        /                 [^]                    [^]
     [^]                 /   \                  /   \
    /   \             [^]     [9]            [^]     [Fuse]
-[^]     [9]         /   \                  /   \
    \            -[^]     [3]           -[^]     [3]
     [1]             \                      \
                      [1]                    [Fuse]


bool FindAtom(NoodOp*,AtomsName,char) ищет заданную терминальную вершину в дереве. Возвращает истину если она там есть.


     X1
    /
-[^]     X2                  arg = 3
    \   /                    AtomsName = num
     [|]
        \
         3

Now terrible

bool SimplifySum(NoodOp*) попытка упростить суммы записанные в дереве. Для заданного узла: если он является суммой двух чисел, то числа складываются. Если данная операция минус, то в ответвлении p1 ищется такой же операнд с плюсом, если он найден, то происходит сокращение слагаемых. Процедура расчитанна на разбалансированное дерево. И в принципе вовсе не нужна. Работает только с текущим узлом. Суммы которые ответвляются от данного узла не рассматривает.

bool FactorTree(NoodOp*) находит общий множитель в логических выражениях и выносит его за скобки. Вложенные выражения не проверяются.

(A ^ B) | (A ^ C) = A ^ ( B | C)
(A | B) ^ (A | C) = A | ( B ^ C)

bool RemoveIdentical(NoodOp*) Для заданной логической операции, пытается найти в поддереве такой же логический операнд и уничтожить его. Принцип следующий : X1 | X2 | X3 если X2 содержит X1, то в X2 вместо X1 можно подставить false, потому как для значения true X2 все равно не будет востребовано. X1 ^ X2 ^ X3 в X2 вместо X1 можно подставить true, т.к. при false выражение все равно не сработает.

         X3                X3                  X3
        /                 /                   /
     [^]               [^]                 [^]
    /                 /                   /
-[^]     X1       -[^]     X1         -[^]     X1
    \   /             \   /               \   /
     [|]     X2        [|]     X2          [|]
        \   /             \   /               \
         [^]               [^]                 X2
            \                 \
             X3                True

     [...]        [RemoveIndentical]     [Wipe]
для анализа и изменения используются процедуры ifContainsZone, ExludeZone.

bool RemoveIdenticalSub1(NoodOp*, NoodOp*) ищет и уничтожает элементы первого дерева в элементах второго поддерева второго дерева.

bool RemoveDual(NoodOp*) Реализует следующее преобразование :

(μ=1 | μ≠1 ^ ξμ-1='5') → (μ=1 ^ ξμ-1='5')

принцип тот же, что и в предыдущей ситуации, но в X2 ищется противоположное условие.

     [=]                  [=]
    /                    /
-[|]     [≠]         -[|]     True
    \   /                \   /
     [^]                  [^]
        \                    \

We go further

Условие {a 0 n} (ξa≠ 'у') преобразуется в так называемый коньюктивный цикл. Цикл считается истиной, если при переборе всех его значений в его теле не возникало лжи. Моя мысль проста до идиотизма : вытащить из тела цикла все что не зависит от его параметра.

[a 0 n] (X1(a) ^ X2) = X2 ^ [a 0 n] (X1(a))
[a 0 n] (X1(a) | X2) = X2 | [a 0 n] (X1(a))

Чтобы все процедуры работы с деревом были универсальны параметры цикла хранятся в виде набора обычных узлов :

{a 3 c} (ξa='5')

bool OpenCycle(NoodOp**) вытягивает из цикла логические операнды не зависящие от переменной цикла. Может полностью уничтожить цикл, если это необходимо.


         [a,0,b]                  X1
        /                        /
-[Cycle]     X1              -[|]         [a,0,b]
        \   /                    \       /
         [|]     a                [Cycle]     a
            \   /                        \   /
             [=]                          [=]
                \                            \
                 3                            3


         [a,0,b]
        /                    1
-[Cycle]     1              /
        \   /           -[=]
         [=]                \
            \                b
             b

bool JustifyCycle(NoodOp*) изменяет границу цикла, если она контролируется в теле цикла

         [a,1,b]
        /                          [a,2,b]
-[Cycle]     X1                   /
        \   /             -[Cycle]     X1
         [^]     1                \   /
            \   /                  [^]
             [=]                      \
                \                      [Fuse]
                 a

... ...

...

Планы (чтоб не забыть)

  1. сделать резиновый стек у компилятора
  2. словарь плохо сделан
  3. more

Взаимная оптимизация правил

...

Оптимизация с учетом вероятности

Статистика

Частота бyкв/бyквосочетаний в рyсском языке:

О, И, Е, А, H, Т, C, В, Р, Л, К, Д, М, П, У, Ы, Я, Б, Г, З, Ь, Ч, Х, Ж, Ш, Ю, Ц, Щ, Э, Ф, CТ, HО, ЕH, ТО, HА, ОВ, HИ, РА, ВО, КО, ОИ, ИИ, ИЕ, ЕИ, ОЕ, ИЯ, HH, CC, CТО, ЕHО, HОВ, ТОВ, ОВО, HАЛ, РАЛ, HИC.

Относительная частота

а 0.062 л 0.035 ц 0.004
б 0.014 м 0.026 ч 0.012
в 0.038 н 0.053 ш 0.006
г 0.013 о 0.090 щ 0.003
д 0.025 п 0.023 ы 0.016
е,ё 0.072 р 0.040 ъ,ь 0.014
ж 0.007 с 0.045 э 0.003
з 0.016 т 0.053 ю 0.006
и 0.062 у 0.021 я 0.018
й 0.010 ф 0.002 . .
к 0.028 х 0.009 . .

Обеспечение надежности системы

...

Index

void CanonTree(NoodOp*)
void Destroy(tree)
void Duplicate(NoodOp*,NoodOp**)
void ExludeZone(NoodOp*,NoodOp*, char new)
void ExpandSet(NoodOp*)
bool FactorTree(NoodOp*)
bool FindAtom(NoodOp*,AtomsName,char)
void FuseTreeP2(NoodOp*)
void HideTreeP1(NoodOp*)
void HideTreeP2(NoodOp*)
char ifCoincedesTree(NoodOp*, NoodOp*)
bool ifContainsZone(NoodOp*, NoodOp*)
bool ifDualCoincedesTree(NoodOp *tree1, NoodOp *tree2)
bool isCycle(char)
bool isDualLogic(char, char)
bool isLogic(char)
bool isSimilar(char, char)
bool isSimmentrion(char)
bool isSummation(char)
bool JustifyCycle(NoodOp*)
void LinearTree(NoodOp*)
bool OpenCycle(NoodOp**)
void OrderTree(NoodOp*)
bool RemoveDual(NoodOp*)
bool RemoveIdentical(NoodOp*)
bool SimplifySum(NoodOp*)
void WipeTree(NoodOp**)

[ to Back >>> ]

Hosted by uCoz