Экспертная система. Логическая задача. Фейерверк
15.2. Головоломка Эйнштейна
Знаменитая головоломка Эйнштейна формулируется следующим образом.
С другой стороны улицы подряд стоят пять домов, каждый своего цвета. В каждом доме живет человек, все пять разных национальностей. Каждый человек предпочитает уникальную марку сигарет, напиток и домашнее животное. Известно, что
- англичанин живет в красном доме;
- у испанца есть собака;
- кофе пьют в зеленом доме;
- украинец пьет чай;
- зеленый дом — первый по правую руку от дома цвета слоновой кости;
- курильщик "Winston" держит улиток;
- сигареты "Kool" курят в желтом доме;
- молоко пьют в среднем доме;
- норвежец живет в крайнем слева доме;
- мужчина, курящий "Chesterfield", живет в доме, соседнем с домом мужчины, у которого есть лиса;
- сигареты "Kool" курят в доме, соседнем с тем, где имеется лошадь;
- мужчина, предпочитающий "Lucky Strike", пьет апельсиновый сок;
- японец курит сигареты "Parliament";
- норвежец живет в доме рядом с голубым домом;
- у одного из мужчин есть зебра;
- один из мужчин пьет воду.
Определите для каждого дома его цвет, а также национальность, любимый напиток, марку сигарет и домашнее животное хозяина [9].
Ниже приводится решение задачи методом "образовать и проверить". Сначала создается список, состоящий из пяти пустых структур, соответствующих пяти домам. Этот список постепенно заполняется в соответствии с условиями, приведенными в задаче: выбирается произвольным образом одна или две структуры (они берутся по очереди), в зависимости от условия, и делается попытка вставить в нее или в них значения из этого условия. Если этого сделать не удается, то делается откат.
open core, console, list
constants
empty : symbol = "".
domains % описание дома
house = h(symbol Colour, symbol Nationality, symbol Pet,
symbol Drink, symbol Cigarette).
class predicates
colour: (house, symbol Colour) -> house determ.
nationality: (house, symbol Nationality) -> house determ.
pet: (house, symbol Pet) -> house determ.
drink: (house, symbol Drink) -> house determ.
cigarette: (house, symbol Cigarette) -> house determ.
unif: (symbol, symbol) determ.
leftmost: (house [out], positive [out], house*) determ.
middle: (house [out], positive [out], house*).
next: (house, positive, house, positive, house*)
nondeterm (o,o,o,o,i).
right: (house, positive, house, positive, house*)
nondeterm (o,o,o,o,i).
right: (house, positive, positive, house, positive, positive, house*)
nondeterm (o,i,o,o,i,o,i).
solve: () -> house* nondeterm.
clauses
colour(h(Cl, N, P, D, S), C) = h(C, N, P, D, S):-
unif(Cl, C).
nationality(h(C, Nt, P, D, S), N) = h(C, N, P, D, S):-
unif(Nt, N).
pet(h(C, N, Pt, D, S), P) = h(C, N, P, D, S):-
unif(Pt, P).
drink(h(C, N, P, Dr, S), D) = h(C, N, P, D, S):-
unif(Dr, D).
cigarette(h(C, N, P, D, Sg), S) = h(C, N, P, D, S):-
unif(Sg, S).
unif(empty, _):- !.
unif(X, X).
% крайний слева дом имеет номер 0 и стоит первым в списке
leftmost(X, 0, [X | _]).
% средний дом находится в середине списка
middle(nth(I, L), I, L):-
I = length(L) div 2.
% пары соседних домов, где дом B находится справа от дома A
right(A, I, B, J, L):-
right(A, 0, I, B, 1, J, L).
right(A, I, I, B, J, J, [A, B | _]).
right(A, C1, I, B, C2, J, [_ | L]):-
right(A, C1 + 1, I, B, C2 + 1, J, L).
% пары соседних домов
next(A, Na, B, Nb, Houses):-
right(A, Na, B, Nb, Houses).
next(A, Na, B, Nb, Houses):-
right(B, Nb, A, Na, Houses).
solve() = Houses20:-
Houses = [h(empty, empty, empty, empty, empty) ||
_ = std::fromTo(0, 4)],
% англичанин живет в красном доме
memberIndex_nd(H1, C1, Houses),
setNth(C1, Houses,
colour(nationality(H1, "Englishman"), "red"), Houses1),
% у испанца есть собака
memberIndex_nd(H2, C2, Houses1),
setNth(C2, Houses1, pet(nationality(H2, "Spaniard"), "dog"),
Houses2),
% кофе пьют в зеленом доме
memberIndex_nd(H3, C3, Houses2),
setNth(C3, Houses2, drink(colour(H3, "green"), "coffee"),
Houses3),
% украинец пьет чай
memberIndex_nd(H4, C4, Houses3),
setNth(C4, Houses3, drink(nationality(H4, "Ukrainian"), "tea"),
Houses4),
% зеленый дом – первый справа от дома цвета слоновой кости
right(H5, C5, H6, C6, Houses4),
setNth(C5, Houses4, colour(H5, "ivory"), Houses5),
setNth(C6, Houses5, colour(H6, "green"), Houses6),
% курильщик "Winston" держит улиток
memberIndex_nd(H7, C7, Houses6),
setNth(C7, Houses6, pet(cigarette(H7, "Winston"), "snails"),
Houses7),
% сигареты "Kool" курят в жёлтом доме
memberIndex_nd(H8, C8, Houses7),
setNth(C8, Houses7, colour(cigarette(H8, "Kool"), "yellow"),
Houses8),
% молоко пьют в среднем доме
middle(H9, C9, Houses8),
setNth(C9, Houses8, drink(H9, "milk"), Houses9),
% норвежец живет в крайнем слева доме
leftmost(H10, C10, Houses9),
setNth(C10, Houses9, nationality(H10, "Norwegian"),
Houses10),
% курящий "Chesterfield", живет по соседству с лисой
next(H11, C11, H12, C12, Houses10),
setNth(C11, Houses10, cigarette(H11, "Chesterfield"),
Houses11),
setNth(C12, Houses11, pet(H12, "fox"), Houses12),
% "Kool" курят в доме, соседнем с домом, где имеется лошадь
next(H13, C13, H14, C14, Houses12),
setNth(C13, Houses12, cigarette(H13, "Kool"), Houses13),
setNth(C14, Houses13, pet(H14, "horse"), Houses14),
% предпочитающий "Lucky Strike" пьет апельсиновый сок
memberIndex_nd(H15, C15, Houses14),
setNth(C15, Houses14, drink(cigarette(H15, "Lucky Strike"),
"orange juice"), Houses15),
% японец курит сигареты "Parliament"
memberIndex_nd(H16, C16, Houses15),
setNth(C16, Houses15, cigarette(nationality(H16, "Japanese"),
"Parliament"), Houses16),
% норвежец живет в доме рядом с голубым домом
next(H17, C17, H18, C18, Houses16),
setNth(C17, Houses16, nationality(H17, "Norwegian"),
Houses17),
setNth(C18, Houses17, colour(H18, "blue"), Houses18),
% у одного из мужчин есть зебра
memberIndex_nd(H19, C19, Houses18),
setNth(C19, Houses18, pet(H19, "zebra"), Houses19),
% один из мужчин пьет воду
memberIndex_nd(H20, C20, Houses19),
setNth(C20, Houses19, drink(H20, "water"), Houses20).
run():-
Houses = solve(),
forAll(Houses, {(h(C, N, P, D, S)):-
writef("%-10%-14%-10%-16%\n", C, N, P, D, S)}),
fail;
_ = readLine().
Пример
15.3.
Решение головоломки Эйнштейна