Экспертная система. Логическая задача. Фейерверк
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. Решение головоломки Эйнштейна