DemonScript: Размещения ящиков
Рассмотрим мир прямоугольных закрытых ящиков различных размеров. Ящики можно вкладывать друг в друга. Когда ящик X находится внутри ящика Y, считается истинным отношение X in Y. Если информации о размерах ящиков нет, то существует достаточно большее число вариантов размещения ящиков друг в друге. Например, для двух ящиков a,b, возможно три варианта: 1) ящик a находится в ящике b; 2) ящик b находится в a; 3) ящики не находятся друг в друге. Для четырёх ящиков таких вариантов 16 и т.д.:
1, 3, 16, 125, 1296, 16807, 262144, 4782969, 100000000, 2357947691,..В общем случае для N ящиков число размещений равно (N+1)(N-1) и образует последовательность A000272. Задача состоит в получении всех этих моделей при помощи логического программирования.
Определим тип отношения и создадим граф с четырмя безымянными узлами:
edges in // объявляем типы рёбер GRAPH.add_nodes(4) // будет 4 узла (ящика)Теперь добавим в объект Mind аксиомы, которые описывают отношение in:
Mind.add( X !in X, // антирефлексивно X in Z & Z in Y -> X in Y, // транзитивно Z in X & Z in Y -> X in Y | Y in X | X==Y, // дерево )Антирефлексивность означает, что ящик X сам в себе находиться не может, а транзитивность подразумевает возможность сколь угодно глубокой вложенности ящиков, подобно матрёшкам. Третья аксиома древесности звучит как: если Z находится и в X, и в Y, то либо X внутри Y, либо Y внутри X, либо это один и тот же ящик.
Идея порождения моделей состоит в следующем. Если в графе между двумя узлами (ящиками) нет ребра in (т.е. оно имеет неопределённое значение Undef), это означает что ящики как могут находится друг в друге (отношение равно True) так могут и не находиться (отношение равно False). Поэтому допустимы две модели со значениями ребра в одной True, а во второй False. Каждая из них, в свою очердь может породить ещё два графа для другого ребра и т.д.
Поэтому, возьмём граф с N узлами без рёбер и поместим его в очередь (массив Open). На каждой итерации из очереди извлекается один граф и в нём ищется неопределённое ребро. Если такого ребра нет, это означает, что сформировалась полностью определённая модель размещения ящиков. Если же ребро найдено, то делаются две копии этого графа. В одном графе ребро помечаем как True, а в другом - как False. Эти графы снова помещаем в очередь и повторяем процедуру до тех пор пока очередь не опустеет.
Перейдём к реализации алгоритма. Создадим двух демонов. Первый будет добавлять в очередь (массив) Open новый граф, устанавливая ребро X in Y в значение Val:
def add_graph(Open, X, Y, Val) { var GV = GRAPH.copy() // делаем копию графа GRAPH = GV // делаем её активной X in Y = Val // меняем ребро Mind.set_graph(GV) // запускаем логический вывод Open.push(GV) // добавляем граф в очередь }Второй демон находит первое неопределённое ребро и порождает два графа, которые после установки ребра помещаются в очередь Open:
def set_edges(Open) { for X in True : // бежим по всем узлам графа if exists(Y, (X in Y) == Undef){ // если есть узел с неопределённым ребром add_graph(Open, X, Y, True) // создаём две копии графа add_graph(Open, X, Y, False) // в которых определяем это ребро return True } return False }
Основной код алгоритма выглядит следующим образом:
var G = GRAPH // для краткости //G[1] in G[2]; // можем задать ограничения Mind.set_graph(G) // устанавливаем антирефлексивность var Open = [ G.copy() ] // помещаем в очередь начальный граф var N_MODELS = 0 // число сгенерённых моделей while !Open.empty() { // пока очередь не пустая GRAPH = Open.pop() // извлекаем из неё граф if !set_edges(Open){ // если не нашли Undef рёбер N_MODELS++ // это финальная модель //out GRAPH // выводим её } } out "Count of graphs: ", N_MODELS // выводим число моделей out System.time(),"ms" // выводим время работы
Описанный выше алгоритм встроен в объект Mind. Ему необходимо передать текущий граф (первый аргумент) и тип ребра (in) которые следует устанавливать в графе в значения True и False при неопределённом значении (Undef):
GRAPH.clear_edges() // убиваем все рёбра System.time(0) // сбрасываем таймер out "Count of graphs: ", out Mind.count_models(GRAPH, in) // Mind подсчитывает число моделей out System.time(),"ms" // выводим время работы
Полный код этого примера находится в файле in_models_calc.ds.