Иерархические модели данных. Деревья в СУБД Cache
3.3.3 Поиск в ширину и в глубину
Оба вида поиска в этом разделе будут демонстрироваться на глобалах. Однако всё сказанное применимо и к локальным многомерным массивам.
Поиск в ширину (функция $ORDER)
В разреженных массивах практически никогда нельзя предугадать следующий индекс на выбранном уровне индексации. Точно также не известны ни предыдущий индекс, ни первый, ни последний индексы на каждом уровне индексации.
Функция $O[RDER] (сокращённо $O) использует в качестве аргумента узел дерева и возвращает значение индекса следующего по порядку элемента. Первый аргумент даёт имя локала или глобала. Второй аргумент указывает направление обхода. Значение +1, оно же значение по умолчанию, определяет движение по возрастанию. Значение —1 задаёт движение по убыванию.
Создадим глобал, изображённый на рисунке 3.26.
s ^G="",^G(-1)=-1,^G(1)=1,^G(7)=7,^G(-1,5)=-15 s ^G(-1,10)=-110,^G(7,"A")="7A",^G(7,"B")="7B"
Обратите внимание, что, например, команда W $O(^G(""),1)
вернёт индекс —1 крайнего левого узла на глубине 1. Команда $O(^G(7),1) вернёт пустой индекс.
Можно считать, что цепочка индексов каждого горизонтального подуровня замыкается на фиктивный пустой индекс, что изображено на на рисунке 3.26.
Начнём движение с второго пустого индекса. Команда W $O^G(7, ""),-1) вернёт индекс крайнего правого узла в правом горизонтальном слое уровня 2. После выполнения команд W $O^G(7,"B"),-1) и $O^G(7,"A"),-1) получим пустой индекс. Цепочка замкнулась.
Для поиска самого левого индекса воспользуйтесь следующими функциями:
- W $O^G(""),1) даёт крайний левый узел на первом уровне дерева
- W $O(^G(""),-1) даёт крайний правый узел на первом уровне дерева
- W $O(^G(-1,""),1) даёт крайний левый узел на втором уровне поддерева, образованного узлом ^G (-1).
Обойти все узлы поддерева на одном уровне можно с помощью циклов, учитывающих имя узла, образующего поддерево, и замкнутость горизонтальных подуровней на узел с пустым индексом (листинг 3.43)
USER>S x="" F S x=$O(^G(x)) Q:x="" W !, x, ?30, ^G(x) -1 -1 1 1 7 7 USER>S x="" F S x=$O(^G(7,x)) Q:x="" W !,x,?30,^G(7,x) A 7A B 7BПример 3.43. Обход горизонтального подуровня
Не забывайте, что после ключевого слова F в цикле без параметров и перед командой W стоят два пробела.
$0RDER пробегает все узлы уровня, включая виртуальные. Однако, приведенный цикл использовать без переделки нельзя, потому что, встретив виртуальный узел, не имеющий значения, он не сможет распечатать его.
Уточним детали. Сортируемые элементы располагают слева направо в порядке возрастания.
Вспомним естественный порядок сортировки. Сначала канонические числа, не содержащие избыточных нулей и знака "+", в порядке возрастания1. Затем неканонические числа. За ними идут слова, начинающиеся с букв латиницы. После них слова, начинающиеся с букв кириллицы.
Для уточнения порядка сортировки одиночных символов наберите следующие две строки
F i=1:1:255 S a($CHAR(i))=$CHAR(i) S x="" F S x=$O(a(x)) Q:x="" W !,x,?30,a(x)
и сами рассмотрите полученную последовательность. Обратите внимание на то, что буквы кириллицы расположены не подряд.
Замечание. Функция $CHAR(i) выдаёт символ с кодом i.
Поиск в глубину (функция $QUERY)
Поиск в глубину производится с помощью функции $Q[ERY], возвращающей в отличие от $0RDER, не следующий индекс, а имя следующего узла. Однако, возвращаются лишь узлы имеющие значение, то есть виртуальные узлы игнорируются. Движение по ним производится, но результаты не выдаются.
При обходе всего глобала начинаем движение с корня. Правила перемещения:
- делаем шаг по крайней левой ветви вниз;
- если это невозможно, смещаемся на один шаг вправо;
- если и это невозможно, смещаемся на один шаг вверх.
При этом необходимо помнить, что движение по горизонтали, как и для $0RDER возможно только по узлам одного подуровня, имеющим общего непосредственного предка.
Для проверки правила несколько изменим глобал ^G, добавив в него узел ^G(1,1,1) так, чтобы образовался виртуальный узел ^G(1,1) (листинг 3.44)
USER>S ^G="", ^G(-1)=-1, ^G(1)=1, ^G(7)=7, ^G(-1, 5)=-15 USER>S ^G(-1, 10)=-110, ^G(7, "A")="7A" USER>S ^G(7, "B")="7B", ^G(1,1,1)=111 USER>S x="^G" F S x=$Q(@x) Q:x="" W x, "=", @x, ! ^G(-1)=-1 ^G(-1, 5)=-15 ^G(-1, 10)=-110 ^G(1)=1 ^G(1,1,1)=111 ^G(7)=7 ^G(7, "A")=7A ^G(7, "B")=7BFПример 3.44. Цикл с функцией $QUERY
После узла ^G(1) был пройден виртуальный узел ^G(1,1), но поскольку он не имеет значения, в выходных данных сведений о нём нет. При следующем повторе цикла отработано движение вниз к узлу ^G(1,1,1).
Мнемоническое изображение правил поиска в глубину с использованием функции $QUERY и подробное обписание обхода дерева ^G из предыдущего примера приведены на рисунке 3.27.
Копирование индексированных переменных (команда MERGE)
Команда MERGE позволяет вклеивать в индексированные переменные копии других индексированных переменных, независимо от того, являются ли они локальными или глобальными. Синтаксис похож на синтаксис присваивания:
Для проверки результатов этой операции удобно воспользоваться программой просмотра глобала ^%G (листинг 3.45). При первом её исполнении в ответ на предложение ввести устройство и ширину строки нажимайте на Enter, вставив значения по умолчанию, затем введите имя глобала. После ввода пустого имени программа прекращает работу.
USER>D ^%G Device: Right margin: 80 => Screen size for padding (0=nopadding) ? 24 => For help on global specifications DO HELP^%G Global ^G ^G "" ^G(-1)=-1 ^G(-1,5)=-15 10)=-110 ^G(1)=1 ^G(1,1,1)=111 ^G(7)=7 ^G(7,"A")=7A "B")=7B Global ^Пример 3.45. Пример использования программы ^%G
Повторы значений программа не печатает. Так что запись " 10)=-110" после записи <^G(_l,5)=-15" следует читать как "^G(_1,10)=-110".
Создадим глобал с узлами ^a(l), ^a(2), ^a(l,l) и локал b("a"), b("b"). Вставим локал b в узел ^a(1) глобала и просмотрим результат операции (листинг 3.46)
Приведённые в "Иерархические модели данных. Деревья в СУБД Cache" сведения о языке Cache ObjectScript достаточны для создания иерархических баз данных, манипуляций данными и выполнения запросов.
В практической работе должны использоваться средства для администрирования базы данных, которые мы, к сожалению, не изучаем.
USER>S ^a="", ^a(1)="", ^a(2)="", ^a(1, 1)="" USER>S b("a")="", b("b")="" USER>MERGE ^a(1)=b USER>D ^%G For help on global specifications DO HELP^%G Global ^a ^a "" ^a(1)="" ^a(1,1)="" "a")="" "b")="" ^a(2)=""Пример 3.46. Пример использования команды MERGE