Решение квадратной головоломки с помощью программирования ограничений

#prolog #clpfd #sicstus-prolog

#пролог #clpfd #sicstus-prolog

Вопрос:

Вопрос: Заполните сетку квадратами (любого размера), которые не соприкасаются и не перекрываются даже по углам. Цифры внизу и справа указывают количество квадратов сетки, заполненных в соответствующем столбце / строке.

Чтобы решить эту проблему, я применил следующие ограничения: размещенные квадраты должны быть непересекающимися, и чтобы убедиться, что количество квадратов сетки правильное, я ограничил сумму длин квадратов, пересекающих данную строку / столбец, равной этому номеру строки / столбца.

Однако выводимым решением является [1, 0, 0, 1] ( [NumSquares, X, Y, SquareSize] ), один квадрат с длиной значения один в координатах (0, 0), и он должен быть тем, который изображен на правом изображении (13 квадратов с разными размерами и координатами).

 :- use_module(library(clpfd)).

:- include('utils.pl').

solve(Rows, Columns, Vars) :-
    % Domain and variables definition

    length(Rows, Size),   

    MaxNumSquares is Size * Size,                
    NumSquares #>= 0,                               
    NumSquares #< MaxNumSquares,      

    length(StartsX, NumSquares),                    
    length(StartsY, NumSquares),                   
    length(SquareSizes, NumSquares),                

    S is Size - 1,           
                           
    domain(StartsX, 0, S),                         
    domain(StartsY, 0, S),                          
    domain(SquareSizes, 1, Size),                  

    construct_squares(Size, StartsX, StartsY, SquareSizes, Squares), 

    % Constraints

    disjoint2(Squares, [margin(0, 0, 1, 1)]),
    lines_constraints(0, Rows, StartsX, SquareSizes),
    lines_constraints(0, Columns, StartsY, SquareSizes),

    % Solution search

    VarsList = [NumSquares, StartsX, StartsY, SquareSizes],
    flatten(VarsList, Vars),
    labeling([], Vars).

construct_squares(_, [], [], [], []). 

construct_squares(Size, [StartX|T1], [StartY|T2], [SquareSize|T3], [square(StartX, SquareSize, StartY, SquareSize)|T4]) :-
    StartX   SquareSize #=< Size,              
    StartY   SquareSize #=< Size,
    construct_squares(Size, T1, T2, T3, T4).  

% Rows and columns NumFilledCells cells constraints

lines_constraints(_, [], _, _).

lines_constraints(Index, [NumFilledCells|T], Starts, SquareSizes) :-
    line_constraints(Index, NumFilledCells, Starts, SquareSizes),
    I is Index   1,
    lines_constraints(I, T, Starts, SquareSizes).

line_constraints(Index, NumFilledCells, Starts, SquareSizes) :-
    findall(
        SquareSize,
        (
            element(N, Starts, Start),  
            element(N, SquareSizes, SquareSize),  
            intersect(Index, Start, SquareSize)
        ),
        Lines),
    sum(Lines, #=, NumFilledCells).
    
% Check if a square intersects a row or column

intersect(Index, Start, SquareSize) :-
    Start #=< Index,
    Index #=< Start   SquareSize.
 

Нераскрытое
Решаемая

Ответ №1:

Проблема в вашем line_constraint/4 предикате. В нем вы размещаете некоторые ограничения clpfd внутри a findall/3 . Это означает, что эти ограничения действительны только внутри findall/3 . Вот способ переписать ваш предикат, который сохраняет опубликованные ограничения (учитывая, что вы используете SICStus, я использую do стиль цикла, который представляет собой просто синтаксический сахар вокруг рекурсивного предиката):

 line_constraints(Index, NumFilledCells, Starts, SquareSizes) :-
    (
      foreach(Start,Starts),
      foreach(SquareSize,SquareSizes),
      foreach(Usage,Usages),
      param(Index)
    do
      Intersect #<=> ( Start #=< Index #/ Index #< Start   SquareSize),
      Usage #= Intersect * SquareSize
    ),
    sum(Usages, #=, NumFilledCells).
 

(Обратите внимание, что я изменил второе неравенство на строгое: конец квадрата находится прямо перед Start SquareSize .)

Как вы, вероятно, испытаете, эта формулировка довольно слаба с точки зрения сокращения пространства поиска. Одним из способов улучшить его (но я сам не пробовал) было бы заменить lines_constraints/4 на некоторые кумулятивные ограничения.

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

1. Я заменил line_constraints предикат, но программа выполняется в бесконечном цикле. Я сделал что-то не так? Я попытался запустить его со следующими входными [2,2,2,2,3,2,2,5,3,4] данными (строки) и [4,5,4,4,0,0,0,4,3,3] (столбцы), которые соответствуют изображению головоломки, которое я отправил

2. Я пробовал с обоими [1,0,1],[1,0,1] и [2,2,0],[2,2,0] , и это дает правильный ответ (ы). Как я уже сказал, при большем вводе это становится очень неэффективным…

3. Похоже, что добавление ограничений, нарушающих симметрию, позволяет решать большие входные данные. Добавьте это где-нибудь в свой solve/3 предикат (после объявления переменных и перед маркировкой): (foreach(X,StartsX), foreach(Y,StartsY), foreach([X,Y],StartsXY) do true),lex_chain(StartsXY),

4. Да, но, похоже, я должен явно указать количество квадратов

5. Странно, у меня нет такой же проблемы. Я добавил дополнительные строки сразу после трех domain/3 звонков. На этом этапе кода количество квадратов уже в любом случае фиксировано ( length(StartsX, NumSquares) вызовом).

Ответ №2:

Поскольку проблема заключалась в количестве квадратов, я установил их максимально возможными (общее количество ячеек, разделенных на четыре, поскольку они должны быть непересекающимися), но допустил, чтобы его ширина / высота были равны нулю, фактически не существовали, а затем допускали ограничение количества квадратовмежду нулем и максимальным количеством квадратов.