Einstein Riddle/Zebra Puzzle in Prolog

einsteinIn this post we solve another logic puzzle deriving rules from the puzzle and stating them in Prolog. And this time it is a rather famous puzzle one: the Zebra puzzle (also called Einstein riddle/puzzle).

The puzzle

The setting is as follows: there is a group of 5 people. Each person lives in exactly one house, has exactly one nationality and exactly one pet, drinks exactly one drink and smokes exactly one brand of cigarettes. Different goals are possible: a famous one is “who drinks water and who owns the zebra”. But one could instead also define the goal to derive for all houses and people: who lives is which house, has which nationality and which pet, drinks which drink and smokes which brand of cigarettes – given the fact, that each of these attributes is unique among the people in our group. This is the question we are going to answer. The following hints are provided (from Wikipedia):

  1.     There are five houses.
  2.     The Englishman lives in the red house.
  3.     The Spaniard owns the dog.
  4.     Coffee is drunk in the green house.
  5.     The Ukrainian drinks tea.
  6.     The green house is immediately to the right of the ivory house.
  7.     The Old Gold smoker owns snails.
  8.     Kools are smoked in the yellow house.
  9.     Milk is drunk in the middle house.
  10.     The Norwegian lives in the first house.
  11.     The man who smokes Chesterfields lives in the house next to the man with the fox.
  12.     Kools are smoked in the house next to the house where the horse is kept.
  13.     The Lucky Strike smoker drinks orange juice.
  14.     The Japanese smokes Parliaments.
  15.     The Norwegian lives next to the blue house.

Puzzle solution in Prolog

We now need to write the knowledge from above as Prolog rules and ask for a solution fulfilling these rules:

% ####################################################################
% Zebra puzzle solver. A house is a list with 6 attributes:
% 1. Color
% 2. Nationality
% 3. Drink
% 4. Cigarettes
% 5. Pet
% 6. The housenumber (fixed by hand to avoid redundant solutions)
%
% Steps to obtain the solution:
%
% 1) Read definition to prolog:
%   ['zebra_puzzle.pl'].
%
% 2) Ask for solutions:
%   solve(H1,H2,H3,H4,H5).
%
% Rainhard Findling
% 09/2015
% ####################################################################

all_members([H],L2) :- member(H,L2).
all_members([H|T],L2) :- member(H,L2), all_members(T, L2).

% ####################################################################
% RULES
% ####################################################################

% rule 1 (5 houses) is implicit for our implementation

rule2(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [red,england,_,_,_,_].

rule3(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [_,spain,_,_,dog,_].

rule4(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [green,_,coffee,_,_,_].

rule5(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [_,ukraine,tea,_,_,_].

rule6(H1,H2,H3,H4,H5) :-
    member(HL,[H1,H2,H3,H4,H5]),
    member(HR,[H1,H2,H3,H4,H5]),
    HL = [white,_,_,_,_,NrL],
    HR = [green,_,_,_,_,NrR],
    NrR-NrL =:= 1.

rule7(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [_,_,_,altemgold,snails,_].

rule8(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [yellow,_,_,kools,_,_].

rule9(_,_,H3,_,_) :-
    H3 = [_,_,milk,_,_,3].

rule10(H1,_,_,_,_) :-
    H1 = [_,norway,_,_,_,1].

rule11(H1,H2,H3,H4,H5) :-
    member(HL,[H1,H2,H3,H4,H5]),
    member(HR,[H1,H2,H3,H4,H5]),
    HL = [_,_,_,chesterfield,_,NrL],
    HR = [_,_,_,_,fox,NrR],
    abs(NrL-NrR,1).

rule12(H1,H2,H3,H4,H5) :-
    member(HL,[H1,H2,H3,H4,H5]),
    member(HR,[H1,H2,H3,H4,H5]),
    HL = [_,_,_,kools,_,NrL],
    HR = [_,_,_,_,horse,NrR],
    abs(NrL-NrR,1).

rule13(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [_,_,orange,luckystrike,_,_].

rule14(H1,H2,H3,H4,H5) :-
    member(H, [H1,H2,H3,H4,H5]),
    H = [_,japan,_,parliament,_,_].

rule15(H1,H2,H3,H4,H5) :-
    member(HL,[H1,H2,H3,H4,H5]),
    member(HR,[H1,H2,H3,H4,H5]),
    HL = [_,norway,_,_,_,NrL],
    HR = [blue,_,_,_,_,NrR],
    abs(NrL-NrR,1).

% ####################################################################
% solve the puzzle
% ####################################################################

solve(H1,H2,H3,H4,H5) :-
    % bind houses to position to avoid multiple solutions with switched variables
    H1 = [H1_col,H1_nat,H1_drink,H1_cig,H1_pet,1],
    H2 = [H2_col,H2_nat,H2_drink,H2_cig,H2_pet,2],
    H3 = [H3_col,H3_nat,H3_drink,H3_cig,H3_pet,3],
    H4 = [H4_col,H4_nat,H4_drink,H4_cig,H4_pet,4],
    H5 = [H5_col,H5_nat,H5_drink,H5_cig,H5_pet,5],
    % the rules
    rule2(H1,H2,H3,H4,H5),
    rule3(H1,H2,H3,H4,H5),
    rule4(H1,H2,H3,H4,H5),
    rule5(H1,H2,H3,H4,H5),
    rule6(H1,H2,H3,H4,H5),
    rule7(H1,H2,H3,H4,H5),
    rule8(H1,H2,H3,H4,H5),
    rule9(H1,H2,H3,H4,H5),
    rule10(H1,H2,H3,H4,H5),
    rule12(H1,H2,H3,H4,H5),
    rule13(H1,H2,H3,H4,H5),
    rule14(H1,H2,H3,H4,H5),
    rule15(H1,H2,H3,H4,H5),
    % for all variable ensure that all values exist
    all_members([white, green, red, blue, yellow], [H1_col,H2_col,H3_col,H4_col,H5_col]),
    all_members([spain, japan, england, ukraine, norway], [H1_nat,H2_nat,H3_nat,H4_nat,H5_nat]),
    all_members([orange, coffee, milk, tea, water], [H1_drink,H2_drink,H3_drink,H4_drink,H5_drink]),
    all_members([luckystrike, parliament, altemgold, chesterfield, kools], [H1_cig,H2_cig,H3_cig,H4_cig,H5_cig]),
    all_members([dog, zebra, snails, horse, fox], [H1_pet,H2_pet,H3_pet,H4_pet,H5_pet]).

If executed, two possible solutions are printed:

['zebra_puzzle.pl'].
solve(H1,H2,H3,H4,H5).

H1 = [yellow, norway, water, kools, zebra, 1],
H2 = [blue, ukraine, tea, chesterfield, horse, 2],
H3 = [red, england, milk, altemgold, snails, 3],
H4 = [white, spain, orange, luckystrike, dog, 4],
H5 = [green, japan, coffee, parliament, fox, 5] ;

H1 = [yellow, norway, water, kools, fox, 1],
H2 = [blue, ukraine, tea, chesterfield, horse, 2],
H3 = [red, england, milk, altemgold, snails, 3],
H4 = [white, spain, orange, luckystrike, dog, 4],
H5 = [green, japan, coffee, parliament, zebra, 5] ;

That’s it, puzzle solved! 😉

SSH Proxy: Server and Client Side of Using an SSH User Without Shell as Proxy Server

April 30, 2016 Leave a comment

Accessing services on an internal network over a proxy reachable from the Internet.

Imagine you want one of your machines to become a proxy for external users to be able to access local resources or the internet as if they were on that machine. You could run a dedicated proxy server for that, but if the machine provides SSH and you want an easy solution, you can use SSH as well – without risking any shell-related issues (like this user also having access to the local file system).

Why would you want stuff like that in the first place? That’s what proxy servers (and most frequently VPNs) are made for: to access country-, company-, or university-internal resources from outside of that network just as you would from the inside.

Server side

The server side is rather easy. Let’s assume we call that user “sshproxy”. The important thing is to not give your proxy user a shell, which we do here during creation of the user:

sudo useradd -m -d /home/sshproxy -s /bin/false sshproxy # create user and home directory, disable shell

The user’s password is disabled by default (you can check that there’s a ! in /etc/shadow for the sshproxy user). If you don’t want to use passwords but private-public keys (which I would recommend): in /home/sshproxy create the .ssh/ folder and the .ssh/authorized_keys file and ensure they’re readable for the sshproxy user. There you need to add the ssh public keys of people that should be allowed to use the ssh proxy. The cool thing about it is that different real life users can make use of the same sshproxy user: you just need to add to manage the keys of real life users for the sshproxy user. Further, you likely don’t want the sshproxy user to be able to change the authorized keys, therefore make the file read only for that user (e.g. root owned + writeable by owner only). If you have an AllowUsers section in your /etc/ssh/sshd_config, you need to add the sshproxy user there and restart ssh. That’s it on the server side.

User side

If you’re one of the users that need to generate their ssh keypair, you can have a look at:

https://help.ubuntu.com/community/SSH/OpenSSH/Keys#Generating_RSA_Keys
(very easy to understand)

https://stribika.github.io/2015/01/04/secure-secure-shell.html
(details that are good to know if you want to have more secure keys = not so easy to crack)

If you use RSA then use at least 2048 bit key length (4096 can’t hurt…), e.g. with ssh-keygen with the “-b 4096” parameter. If you’re on a Windows machine: you can do the same using e.g. Putty. Don’t forget to use a good password to protect your private key file (usually the “id_rsa” file) and never disclose it – servers only need your public key (usually “id_rsa.pub”).

Using the proxy via Linux shell

Everything’s built in, just use the following command.

ssh -D 8080 -N sshproxy@YOUR-IP-OR-URL -p YOUR-PORT

This command opens the local port 8080 (on the client machine) for proxy tasks. YOUR-PORT-URL and YOUR-PORT correspond to the SSH server running on the server machine. If everything worked fine you won’t get any response in the shell – just leave the terminal open. Make sure your private key file is in ~/.ssh/id_rsa or provide it explicitly with “-i”. If you can’t access the server try removing the “-N”. Then you should see that the server logs you in and out immediately (this means everything works fine from the SSH, proxy and tunnel side – if you add -N again, you should be fine therefore). You can check your local opened ports with nmap (“nmap localhost”). If port 8080 is not in the list before login and opened after login your tunnel works.

Using the proxy via Putty

  • address: YOUR-IP-OR-URL (where SSH server is running)
  • port: YOUR-PORT (where SSH server is running)
  • user: sshproxy
  • Enable the check box “Don’t start a shell or command at all” in “Connection-SSH”
  • Specify your private key in “Auth”
  • Tunnels: add a “dynamic tunnel” on “local port 8080”, leave destination open (should state “D8080” in Putty after you apply)

Sending data to your local port

As you now have a tunnel on port 8080 on your local machine open, you need to send request to this port, instead of your standard gateway. E.g. for your browser you can achieve that configuring the browser to use a proxy (e.g. Firefox: FoxyProxy AddOn: set address “localhost” and port “8080”).

Btw: you can do other cool things with that proxy as well, such as reverse port tunnelling, circumventing firewalls etc. But you should ensure that this is allowed in the company/country you’re in before you get yourself into troubles.

Repair PDFLaTeX generated pdf using GhostScript (Adobe Acrobat Reader error 131)

February 28, 2016 Leave a comment

I tend to do presentations using LaTeX and Beamer, while working on Linux and using TeXLive as LaTeX distribution – which all work fine. But sometimes I need to share these PDFLaTeX compiled presentations with people using Windows and Adobe Acrobat Reader as their pdf viewer. The feedback I usually get back: your pdf is broken, error 131. And frankly, that seems to be true.

Opening PDFLaTeX generated pdf files with Windows Adobe Acrobat Reader results in error 131 – more precisely in the displayed error message “There was a problem reading this document (131)”. Other pdf viewers don’t complain about the pdf, just Adobe Acrobat Reader. A quick solution to repair the pdf file is by using GhostScript:

gs -dSAFER -dBATCH -dNOPAUSE  -sDEVICE=pdfwrite -sOutputFile=output.pdf input.pdf</code></pre>

Voilà, the pdf file can be opened using Adobe Acrobat Reader – although, the source of the problem still exists of course (generating a pdf file not adhering the standard in the first place).

Git security: enabe fsckobjects in ~/.gitconfig:

February 3, 2016 Leave a comment

In order to prevent possible tampering with code in git repositories you work with (e.g. malicious manipulation of objects during clone, fetch, push…), check if these lines exist in your ~/.gitconfig and add them, if they don’t:

[transfer]
fsckobjects = true
[fetch]
fsckobjects = true
[receive]
fsckObjects = true

These enable git checking transferred objects for their integrity using their computed hashes.

Original idea from here: https://groups.google.com/forum/#!topic/binary-transparency/f-BI4o8HZW0
(and the corresponding bug on Debian here: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813157)

Solving Logic Puzzles in Prolog: Puzzle 3 of 3

December 27, 2015 Leave a comment

In this post we solve another small logic puzzle in Prolog, similar to the previous two logic puzzles (these are available here and here).

The puzzle

There are four researchers: Ainsley, Madeline, Sophie and Theodore. The goal is to find out their sports competition discipline, birth year and research interests (while knowing that each of the mentioned attributes is different amongst them). In order so solve the puzzle, a couple of hints is provided from which the solution can be derived:

  1. The competition for Theodore or Ainsley is javelin.
  2. The researcher interested in hurricane research has relay as sports subject.
  3. Sophie is not the researcher who has relay as sports subject.
  4. Either the researcher who has longjump as sports subject or Theodore was born in 1933 – the other one was born in 1921.
  5. The researcher with relay as sports subject was born before Theodore.
  6. The researcher interested in wildfires was born after the researcher doing longjumps for sports.
  7. Neither Sophie nor Ainsley are researching wildfires.
  8. The researcher interested in earthquakes has either hurdle or javelin as sports interest.
  9. The researcher interested in earthquakes was born in 1933.
  10. Madeline was not born in 1921.

Solving the puzzle in Prolog

In our Prolog implementation we ask for a solution that a) ensures that all sports subjects, research interests and birth years exist once and b) fulfills the rules mentioned above:

% ####################################################################
% Logic puzzle solver.
%
% Read definition to prolog:
%   ['puzzle.pl'].
%
% 2) Ask for solutions:
%   solve(Ainsley, Madeline, Sophie, Theodore).
%
% Rainhard Findling
% 10/2015
% ####################################################################
all_members([H],L2) :- member(H,L2).
all_members([H|T],L2) :- member(H,L2), all_members(T, L2).

and([H]) :- H.
and([H|T]) :- H, and(T).
or([H]) :- H,!.
or([H|_]) :- H,!.
or([_|T]) :- or(T).

solve(Ainsley, Madeline, Sophie, Theodore) :-
    All = [Ainsley, Madeline, Sophie, Theodore],
    Ainsley = [Ainsley_Year, Ainsley_Competition, Ainsley_Disaster],
    Madeline = [Madeline_Year, Madeline_Competition, Madeline_Disaster],
    Sophie = [Sophie_Year, Sophie_Competition, Sophie_Disaster],
    Theodore = [Theodore_Year, Theodore_Competition, Theodore_Disaster],
    % we know what all variable must be
    all_members([1920, 1921, 1933, 1973], [Ainsley_Year, Madeline_Year, Sophie_Year, Theodore_Year]),
    all_members([hurdle, relay, javelin, longjump], [Ainsley_Competition, Madeline_Competition, Sophie_Competition, Theodore_Competition]),
    all_members([earthquakes, hurricanes, tornados, wildfires], [Ainsley_Disaster, Madeline_Disaster, Sophie_Disaster, Theodore_Disaster]),
    % c1
    or([Ainsley_Competition = javelin, Theodore_Competition = javelin]),
    % c2
    member([_, relay, hurricanes], All),
    % c3
    not(Sophie_Competition = relay),
    % c4
    member([C4_Year, longjump, _], All),
    or([and([Theodore_Year = 1921, C4_Year = 1933]), and([Theodore_Year = 1933, C4_Year = 1921])]),
    % c5
    member([C5_Year, relay, _], All),
    Theodore_Year > C5_Year,
    % c6
    member([C6_1_Year, longjump, _], All),
    member([C6_2_Year, _, wildfires], All),
    C6_1_Year < C6_2_Year,
    % c7
    not(Sophie_Disaster = wildfires),
    not(Ainsley_Disaster = wildfires),
    % c8
    member([_, C8_competition, earthquakes], All),
    not(C8_competition = hurdle),
    not(C8_competition = javelin),
    % c9
    member([1933, _, earthquakes], All),
    % c10
    not(Madeline_Year = 1921).

Using this implementation, if we ask for a solution, the single possible solution is presented:

['puzzle.prolog'].
% puzzle.prolog compiled 0.00 sec, 13 clauses
true.

?- solve(Ainsley, Madeline, Sophie, Theodore).
Ainsley = [1920, relay, hurricanes],
Madeline = [1973, hurdle, wildfires],
Sophie = [1933, longjump, earthquakes],
Theodore = [1921, javelin, tornados] ;

Solving Logic Puzzles in Prolog: Puzzle 2 of 3

November 29, 2015 1 comment

This post is about solving the 2nd of 3 small logic puzzles in Prolog. The previous post is available here.

The puzzle

Eilen, Ada, Verena and Jenny participated in a painting competition. Find out who painted which subject and who took which place in the competition, using the hints provided. Hints:

    1. Eilen painted a constable and was not last in the competition
    2. Jenny took the third place
    3. The person painting the Monet took the first place
    4. Ada beat the person painting a talyor, and the person painting a Van Gogh beat Vera

Solving the puzzle in Prolog

We just translate the hints into rules that ensure that a) places 1-4 and all mentioned painting subjects exist in our solution and b) the mentioned hints are fulfilled:

% ####################################################################
% Logic puzzle solver.
%
% Read definition to prolog:
% ['puzzle.pl'].
%
% 2) Ask for solutions:
% solve(Eilen, Ada, Vera, Jenny).
%
% Rainhard Findling
% 10/2015
% ####################################################################
all_members([H],L2) :- member(H,L2).
all_members([H|T],L2) :- member(H,L2), all_members(T, L2).

solve(Eilen, Ada, Vera, Jenny) :-
% 4 members
All = [Eilen, Ada, Vera, Jenny],
Eilen = [Eilen_place, Eilen_subject],
Ada = [Ada_place, Ada_subject],
Vera = [Vera_place, Vera_subject],
Jenny = [Jenny_place, Jenny_subject],
% we know max place is 4th
all_members([1,2,3,4], [Eilen_place, Ada_place, Vera_place, Jenny_place]),
% we know existing paintings
all_members([constable,taylor,fangogh,monet], [Eilen_subject, Ada_subject, Vera_subject, Jenny_subject]),
% #3 1st was monet
member([1, monet], All),
% #2 Jenny got third
member([3, Jenny_subject], All),
% #1 Eilen painted a constable and was not last
member([Eilen_place, constable], All),
4 > Eilen_place,
% #4 ada beat the taylor painter...
member([Ada_place, Ada_subject], All),
member([H2_taylor_place, taylor], All),
Ada_place < H2_taylor_place, 
% #4 ...and the van gogh painter beat vera member([H4_fangogh_place, fangogh], All), member([Vera_place, Vera_subject], All), Vera_place > H4_fangogh_place.

If we ask for a solution, the single valid solution is presented:

?- solve(Eilen, Ada, Vera, Jenny).
Eilen = [2, constable],
Ada = [1, monet],
Vera = [4, taylor],
Jenny = [3, fangogh] ;

And again, that’s it – problem solved!

Solving Logic Puzzles in Prolog: Puzzle 1 of 3

October 31, 2015 6 comments

Recently I played around with Prolog again to solve 3 small logic puzzles. This post is about solving the first puzzle. This first puzzle consists of 2 individual puzzles that follow the exact same internal structure, so can be solved the exact same way of stating rules in Prolog (just with different content).

The small puzzle: problem and question

There are 4 students: Carrie, Erma, Ora und Tracy. Each has one scholarship and one major subject they study. The goal is to find out which student has which scholarship and studies which subject (with all scholarships and majors being different from each other) from the hints provided. The available scholarships are: 25000, 30000, 35000 and 40000 USD. The available majors are: Astronomy, English, Philosophy, Physics. The following hints are given to solve the problem:

  1. The student who studies Astronomy gets a smaller scholarship than Ora.
  2. Ora is either the one who studies English or the one who studies Philosophy.
  3. Erna has a 10000 USD bigger scholarship than Carrie.
  4. Tracy has a bigger scholarship than the student that studies English.

The small puzzle: the Prolog solution

One way of solving this puzzle in Prolog to ask for a solution (which in our example consists of the 4 students and their associated attributes) that fulfills the rules that we can derive from the 4 hints from above. This is actually pretty short and easy in Prolog syntax:

% ####################################################################
% Logic puzzle solver.
%
% Read definition to prolog:
%   ['puzzle.pl'].
%
% 2) Ask for solutions:
%   solve(Carrie,Erma,Ora,Tracy).
%
% Rainhard Findling
% 10/2015
% ####################################################################

all_members([H],L2) :- member(H,L2).
all_members([H|T],L2) :- member(H,L2), all_members(T, L2).

or([H]) :- H,!.
or([H|_]) :- H,!.
or([_|T]) :- or(T).

solve(Carrie,Erma,Ora,Tracy) :-
% all students
Carrie = [Carrie_scholarship, Carrie_major],
Erma = [Erma_scholarship, Erma_major],
Ora = [Ora_scholarship, Ora_major],
Tracy = [Tracy_scholarship, Tracy_major],
% grouping
All = [Carrie,Erma,Ora,Tracy],
% ensure all values exist once
all_members([25, 30, 35, 40], [Carrie_scholarship, Erma_scholarship, Ora_scholarship, Tracy_scholarship]),
all_members([astronomy, english, philosophy, physics], [Carrie_major, Erma_major, Ora_major, Tracy_major]),
% clue 1
member([C1_scholarship,astronomy], All),
Ora_scholarship > C1_scholarship,
% clue 2
or([Ora_major = english, Ora_major = philosophy]),
% clue 3
member([C3_scholarship, physics], All),
C3_scholarship - Carrie_scholarship =:= 5,
% clue 4
Erma_scholarship - Carrie_scholarship =:= 10,
% clue 5
member([C5_scholarship, english], All),
Tracy_scholarship > C5_scholarship.

If we ask for the solution, the single possible solution is presented:

?- solve(Carrie,Erma,Ora,Tracy).
Carrie = [25, english],
Erma = [35, astronomy],
Ora = [40, philosophy],
Tracy = [30, physics] ;

The slightly bigger puzzle: problem and question

There are 5 car renting contracts. Each has a duration, a pickup location, a car brand and a customer associated to it. The goal is to find out for all contracts, what the customer, duration, car brand and pickup location is (again given the fact that each attribute is unique amongst the contracts). The pickup locations are: Brownfield, Durham, Iowa Falls, Los Altos and Redding. The car brands are: Dodge, Fiat, Hyundai, Jeep and Nissan. The contract duration are 2, 3, 4, 5 and 6 days. And the customer names are Freda, Opal, Penny, Sarah and Vicky. The following hints are given to solve the problem:

  1. The contracts of Vicky, the one with pickup location in Los Altos, the one with pickup location in Durham and the one with the Fiat are all different contracts.
  2. The contract with the Jeep is not in Iowa Falls.
  3. The contract of Vicky and the one with the Nissan are picked up in either Los Altos or Redding.
  4. Penny’s contract is not for 6 days.
  5. The contract in Iowa Falls is for 5 days.
  6. The contract with the Durham is 3 days longer than the contract of Opal.
  7. Of the contract with Nissan and the 2 day contract, one is picked up in Redding and the other is Freda’s contract.
  8. The contract with the Jeep is not for 6 days.
  9. The contract of Opal is 1 day longer than the one with the Hyundai.

The slightly bigger puzzle: the Prolog solution

Again, we can define a solution to fulfill the stated rules:

% ####################################################################
% Logic puzzle solver.
%
% Read definition to prolog:
%   ['puzzle.pl'].
%
% 2) Ask for solutions:
%   solve(Freda, Opal, Penny, Sarah, Vicky).
%
% Rainhard Findling
% 10/2015
% ####################################################################

all_members([H],L2) :- member(H,L2).
all_members([H|T],L2) :- member(H,L2), all_members(T, L2).

all_not([H]) :- not(H).
all_not([H|T]) :- not(H), all_not(T).

all_not_members([H],L2) :- not(member(H,L2)).
all_not_members([H|T],L2) :- not(member(H,L2)), all_not_members(T, L2).

and([H]) :- H.
and([H|T]) :- H, and(T).
or([H]) :- H,!.
or([H|_]) :- H,!.
or([_|T]) :- or(T).

solve(Freda, Opal, Penny, Sarah, Vicky) :-
    % all runners
    Freda = [Freda_car, Freda_location, Freda_days],
    Opal = [Opal_car, Opal_location, Opal_days],
    Penny = [Penny_car, Penny_location, Penny_days],
    Sarah = [Sarah_car, Sarah_location, Sarah_days],
    Vicky = [Vicky_car, Vicky_location, Vicky_days],
    % grouping
    All = [Freda, Opal, Penny, Sarah, Vicky],
    % ensure all values exist once
    all_members([dodge, fiat, hyundai, jeep, nissan], [Freda_car, Opal_car, Penny_car, Sarah_car, Vicky_car]),
    all_members([brownfield, durham, iowafalls, losaltos, redding], [Freda_location, Opal_location, Penny_location, Sarah_location, Vicky_location]),
    all_members([2,3,4,5,6], [Freda_days, Opal_days, Penny_days, Sarah_days, Vicky_days]),
    % clues from easy (fast) to hard (slow)
    % clue 5
    member([_,iowafalls,5], All),
    % clue 9
    member([hyundai,_,C9_days], All),
    Opal_days - C9_days =:= 1,
    % clue 6
    member([_,durham,C6_days], All),
    C6_days - Opal_days =:= 3,
    % clue 4
    not(Penny_days = 6),
    % clue 2
    member([C2_car,iowafalls,_], All),
    not(C2_car = jeep),
    % clue 8
    member([C8_car,_,6], All),
    not(C8_car = jeep),
    % clue 3
    member([nissan,C3_location,_], All),
    or([and([C3_location = losaltos, Vicky_location = redding]), and([C3_location = redding, Vicky_location = losaltos])]),
    % clue 7
    member([nissan, C7_1_location,_], All),
    member([_,C7_2_location, 2], All),
    or([and([Freda_location = C7_1_location, C7_2_location = redding]), and([Freda_location = C7_2_location, C7_1_location = redding])]),
    % clue 1
    member([C1_1_car,losaltos,_], All),
    member([C1_2_car,durham,_], All),
    member([fiat,C1_3_location,_], All),
    all_not_members([C1_1_car, C1_2_car, Vicky_car],[fiat]), %car
    all_not_members([C1_3_location, Vicky_location],[losaltos, durham]). %location

… and ask for it, which again presents the single solution:

?- solve(Freda, Opal, Penny, Sarah, Vicky).
Freda = [nissan, losaltos, 4],
Opal = [jeep, brownfield, 3],
Penny = [fiat, iowafalls, 5],
Sarah = [dodge, durham, 6],
Vicky = [hyundai, redding, 2] ;