Программирование ограничений, подходящее для извлечения отношений OneToMany из записей - PullRequest
2 голосов
/ 02 июля 2019

Может быть, кто-то может помочь мне решить проблему с Прологом или любым языком программирования с ограничениями. Представьте себе таблицу проектов (школьные проекты, где ученики что-то делают со своими матерями). В каждом проекте участвуют один или несколько детей. Для каждого ребенка мы храним его имя и имя его матери. Но для каждого проекта есть только одна ячейка, содержащая всех матерей, и одна ячейка, содержащая всех детей. Обе ячейки не обязательно упорядочены одинаково.

Пример:

+-----------+-----------+------------+
|           |           |            |
|   Project |   Parents |   Children |
|           |           |            |
+-----------+-----------+------------+
|           |           |            |
|   1       |   Jane;   |   Brian;   |
|           |   Claire  |   Stephen  |
|           |           |            |
+-----------+-----------+------------+
|           |           |            |
|   2       |   Claire; |   Emma;    |
|           |   Jane    |   William  |
|           |           |            |
+-----------+-----------+------------+
|           |           |            |
|   3       |   Jane;   |   William; |
|           |   Claire  |   James    |
|           |           |            |
+-----------+-----------+------------+
|           |           |            |
|   4       |   Jane;   |   Brian;   |
|           |   Sophia; |   James;   |
|           |   Claire  |   Isabella |
|           |           |            |
+-----------+-----------+------------+
|           |           |            |
|   4       |   Claire  |   Brian    |
|           |           |            |
+-----------+-----------+------------+
|           |           |            |
|   5       |   Jane    |   Emma     |
|           |           |            |
+-----------+-----------+------------+

Надеюсь, этот пример визуализирует проблему. Как я уже сказал, обе ячейки содержат только имена, разделенные разделителем, но не обязательно упорядочены одинаковым образом. Таким образом, для технических приложений вы должны преобразовать данные в это:

+-------------+-----------+----------+
|   Project   |   Name    |   Role   |
+-------------+-----------+----------+
|   1         |   Jane    |   Mother |
+-------------+-----------+----------+
|   1         |   Claire  |   Mother |
+-------------+-----------+----------+
|   1         |   Brian   |   Child  |
+-------------+-----------+----------+
|   1         |   Stephen |   Child  |
+-------------+-----------+----------+
|   2         |   Jane    |   Mother |
+-------------+-----------+----------+
|   2         |   Claire  |   Mother |
+-------------+-----------+----------+
|   2         |   Emma    |   Child  |
+-------------+-----------+----------+
|   2         |   William |   Child  |
+-------------+-----------+----------+
|             |           |          |
|                                    |
|              And so on             |

Количество родителей и детей одинаково для каждого проекта. Таким образом, для каждой сделки у нас есть n матерей и n детей, и каждая мама принадлежит ровно одному ребенку. С этими ограничениями можно назначить каждую мать всем своим детям с помощью логического вывода, начиная с проектов, в которых участвует только один ребенок (то есть 4 и 5).

Результаты:

У Джейн есть Эмма, Стивен и Джеймс;

У Клэр Брайан и Уильям;

София имеет Изабеллу

Мне интересно, как это можно решить с помощью программирования ограничений. Кроме того, набор данных может быть недоопределен, и мне интересно, можно ли выделить записи, которые при ручном решении (то есть, когда назначения «мать-дитя» выполняются вручную) сломали бы недоопределение.

Ответы [ 3 ]

1 голос
/ 03 июля 2019

Это моя первая программа CHR, поэтому я надеюсь, что кто-нибудь придет и даст мне несколько советов, как ее улучшить.

Я думаю, что вам нужно расширить все списки в факты.Оттуда, если вы знаете, что у проекта есть только один родитель и один ребенок, вы можете установить родительские отношения из этого.Кроме того, если у вас есть отношения родитель-ребенок, вы можете удалить этот набор из других фактов в других проектах и ​​уменьшить количество проблем на единицу.В конце концов вы поймете все, что можете.Единственная разница между полностью определенным набором данных и не полностью определенным состоит в том, насколько далеко может пойти это сокращение.Если это не совсем так, это оставит некоторые факты, чтобы вы могли видеть, какие проекты / родители / дети все еще создают неоднозначность.

:- use_module(library(chr)).

:- chr_constraint project/3, project_parent/2, project_child/2, 
   project_parents/2, project_children/2, project_size/2, parent/2.

%% turn a project into a fact about its size plus 
%% facts for each parent and child in this project
project(N, Parents, Children) <=>
    length(Parents, Len),
    project_size(N, Len),
    project_parents(N, Parents),
    project_children(N, Children).

%% expand the list of parents for this project into a fact per parent per project
project_parents(_, []) <=> true.
project_parents(N, [Parent|Parents]) <=>
    project_parent(N, Parent),
    project_parents(N, Parents).

%% same for the children
project_children(_, []) <=> true.
project_children(N, [Child|Children]) <=>
    project_child(N, Child),
    project_children(N, Children).

%% a single parent-child combo on a project is exactly what we need
one_parent @ project_size(Project, 1), 
             project_parent(Project, Parent), 
             project_child(Project, Child) <=>
    parent(Parent, Child).

%% if I have a parent relationship for project of size N,
%% remove this parent and child from the project and decrease
%% the number of parents and children by one
parent_det @ parent(Parent, Child) \ project_size(Project, N), 
                                     project_parent(Project, Parent), 
                                     project_child(Project, Child) <=>
    succ(N0, N),
    project_size(Project, N0).

Я запустил это на вашем примере, набрав main/0 предикат для этого:

main :-
    project(1, [jane, claire], [brian, stephen]),
    project(2, [claire, jane], [emma, william]),
    project(3, [jane, claire], [william, james]),
    project(4, [jane, sophia, claire], [brian, james, isabella]),
    project(5, [claire], [brian]),
    project(6, [jane], [emma]).

Это выводит:

parent(sophia, isabella),
parent(jane, james),
parent(claire, william),
parent(jane, emma),
parent(jane, stephen),
parent(claire, brian).

Чтобы продемонстрировать неполное определение, я добавил седьмой проект:

project(7, [sally,sandy], [grace,miriam]).

Затем программавыводит это:

project_parent(7, sandy),
project_parent(7, sally),
project_child(7, miriam),
project_child(7, grace),
project_size(7, 2),
parent(sophia, isabella),
parent(jane, james),
parent(claire, william),
parent(jane, emma),
parent(jane, stephen),
parent(claire, brian).

Как видите, любой оставшийся project_size/2 говорит о количестве элементов, которые предстоит решить (в седьмом проекте еще предстоит определить два родителя / потомка), а вывернуть точно родителей / детей, которые еще предстоит обработать, а также все parent/2 отношения, которые можно определить.

Я очень доволен этим результатом, но, надеюсь, другие могут прийти и улучшить мойcode!

Edit : у моего кода есть недостаток, который был обнаружен в списке рассылки, что некоторые входные данные не будут сходиться, даже если решениеможет быть вычислено, например:

project(1,[jane,claire],[brian, stephan]),
project(2,[jane,emma],[stephan, jones]).

Для получения дополнительной информации см. Решение Иана , которое использует пересечение множества для определения отображения.

1 голос
/ 12 июля 2019

Немного не по теме, но с Руководство по SWI-Prolog :

Простой Пролог можно рассматривать как CLP (H), где H обозначает Гербранд термины. В этой области = / 2 и dif / 2 являются наиболее важными ограничения, которые выражают, соответственно, равенство и неравенство термины.

Я чувствую себя уполномоченным предложить решение Prolog, более общее, чем предложенный вами алгоритм (постепенно сокращать отношения, основанные на однозначных отношениях):

solve2(Projects,ParentsChildren) :-
    foldl([_-Ps-Cs,L,L1]>>try_links(Ps,Cs,L,L1),Projects,[],ChildrenParent),
    transpose_pairs(ChildrenParent,ParentsChildrenFlat),
    group_pairs_by_key(ParentsChildrenFlat,ParentsChildren).

try_links([],[],Linked,Linked).
try_links(Ps,Cs,Linked,Linked2) :-
    select(P,Ps,Ps1),
    select(C,Cs,Cs1),
    link(C,P,Linked,Linked1),
    try_links(Ps1,Cs1,Linked1,Linked2).

link(C,P,Assigned,Assigned1) :-
    (   memberchk(C-Q,Assigned)
    ->  P==Q,
        Assigned1=Assigned
    ;   Assigned1=[C-P|Assigned]
    ).

Принимает данные в естественном формате, например

data(1,
    [1-[jane,claire]-[brian,stephen]
    ,2-[claire,jane]-[emma,william]
    ,3-[jane,claire]-[william,james]
    ,4-[jane,sophia,claire]-[brian,james,isabella]
    ,5-[claire]-[brian]
    ,6-[jane]-[emma]
    ]).
data(2,
    [1-[jane,claire]-[brian,stephen]
    ,2-[claire,jane]-[emma,william]
    ,3-[jane,claire]-[william,james]
    ,4-[jane,sophia,claire]-[brian,james,isabella]
    ,5-[claire]-[brian]
    ,6-[jane]-[emma]
    ,7-[sally,sandy]-[grace,miriam]
    ]).

?- data(2,Ps),solve2(Ps,S).
Ps = [1-[jane, claire]-[brian, stephen], 2-[claire, jane]-[emma, william], 3-[jane, claire]-[william, james], 4-[jane, sophia, claire]-[brian, james, isabella], 5-[claire]-[brian], 6-[jane]-[emma], 7-[...|...]-[grace|...]],
S = [claire-[william, brian], jane-[james, emma, stephen], sally-[grace], sandy-[miriam], sophia-[isabella]].
0 голосов
/ 02 июля 2019

Я не уверен, что понимаю все требования проблемы, но вот модель программирования ограничений в MiniZinc (http://www.minizinc.org/). Полная модель здесь: http://hakank.org/minizinc/one_to_many.mzn.

ПОЗЖЕ ПРИМЕЧАНИЕ: первая версия ограничений проекта, где не правильно. Я удалил неправильный код. См. Историю изменений для исходного ответа.

enum mothers = {jane,claire,sophia};
enum children = {brian,stephen,emma,william,james,isabella};      

% decision variables

% who is the mother of this child?
array[children] of var mothers: x;


solve satisfy;

constraint
  % All mothers has at least one child
  forall(m in mothers) (
    exists(c in children) (
      x[c] = m
    )
  )
;

constraint
% NOTE: This is a more correct version of the project constraints.
% project 1
(
  ( x[brian] = jane /\ x[stephen] = claire) \/
  ( x[stephen] = jane /\ x[brian] = claire)
) 
/\
% project 2
(
  ( x[emma] = claire /\ x[william] = jane) \/
  ( x[william] = claire /\ x[emma] = jane) 
)
/\
% project 3
(
  ( x[william] = claire /\ x[james] = jane) \/
  ( x[james] = claire /\ x[william] = jane) 
)
/\
% project 4
( 
  ( x[brian] = jane /\ x[james] = sophia /\ x[isabella] = claire) \/
  ( x[james] = jane /\ x[brian] = sophia /\ x[isabella] = claire) \/
  ( x[james] = jane /\ x[isabella] = sophia /\ x[brian] = claire) \/
  ( x[brian] = jane /\ x[isabella] = sophia /\ x[james] = claire) \/
  ( x[isabella] = jane /\ x[brian] = sophia /\ x[james] = claire) \/
  ( x[isabella] = jane /\ x[james] = sophia /\ x[brian] = claire) 
)
/\

% project 4(sic!)
( x[brian] = claire) /\

% project 5
( x[emma] = jane)
;


output [
  "\(c): \(x[c])\n"
  | c in children
];

Уникальное решение -

brian: claire
stephen: jane
emma: jane
william: claire
james: jane
isabella: sophia

Edit2: Вот более общее решение. См. http://hakank.org/minizinc/one_to_many.mzn для полной модели.

include "globals.mzn"; 

enum mothers = {jane,claire,sophia};
enum children = {brian,stephen,emma,william,james,isabella};      

% decision variables
% who is the mother of this child?
array[children] of var mothers: x;

% combine all the combinations of mothers and children in a project
predicate check(array[int] of mothers: mm, array[int] of children: cc) =
  let {
    int: n = length(mm);
    array[1..n] of var 1..n: y;
  } in
  all_different(y) /\
  forall(i in 1..n) (
     x[cc[i]] = mm[y[i]]
  )
;    

solve satisfy;

constraint
% All mothers has at least one child.
forall(m in mothers) (
  exists(c in children) (
    x[c] = m
  )
)
;


constraint
% project 1    
check([jane,claire], [brian,stephen]) /\
% project 2
check([claire,jane],[emma,william]) /\
% project 3
check([claire,jane],[william,james]) /\
% project 4
check([claire,sophia,jane],[brian,james,isabella]) /\
% project 4(sic!)
check([claire],[brian]) /\
% project 5
check([jane],[emma])
;

output [
 "\(c): \(x[c])\n"
 | c in children
];

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

predicate check(array[int] of mothers: mm, array[int] of children: cc) =
   let {
     int: n = length(mm);
     array[1..n] of var 1..n: y;
  } in
  all_different(y) /\
  forall(i in 1..n) (
    x[cc[i]] = mm[y[i]]
  )
;    

Он использует глобальное ограничение all_different(y), чтобы гарантировать, что mm[y[i]] является одной из матерей в mm, а затем присваивает `i 'ребенка этой конкретной матери.

...