Created
August 6, 2021 16:49
-
-
Save carwash/350ea7b217dc3fe9e80f3b6e92de9373 to your computer and use it in GitHub Desktop.
The Countdown numbers game, solved in Prolog. <https://en.wikipedia.org/wiki/Countdown_(game_show)#Numbers_round>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:- encoding(utf8). | |
:- use_module(library(apply)). | |
:- use_module(library(clpfd)). | |
:- use_module(library(lists)). | |
:- use_module(library(pairs)). | |
/* | |
The Countdown numbers game, solved in Prolog. | |
https://en.wikipedia.org/wiki/Countdown_(game_show)#Numbers_round | |
- Finds exact solutions only; | |
- Lists all unique solutions, shortest first; | |
Where Ns is a list of numbers, and T is the target number: | |
?- countdown(Ns,T). | |
E.g.: | |
?- countdown([25,50,75,100,3,6],952). | |
7 solutions found. | |
… | |
List candidate targets: | |
?- countdown([25,50,75,100,3,6],T). | |
List all possible puzzles: | |
?- valid_puzzle(Ns,T), label([T|Ns]). | |
List all possible puzzles and solutions: | |
?- countdown(Ns,T). | |
*/ | |
countdown(Ns,T) :- | |
valid_puzzle(Ns,T), % Comment out to remove Countdown rules and generalise for all integers and list lengths | |
label([T|Ns]), | |
bagof(Steps, step(Ns,T,Steps), Solutions0), | |
dedup(Solutions0,Solutions), | |
length(Solutions,No_solutions), | |
write(No_solutions), writeln(' solutions found.'), nl, | |
list_length_sorted(Solutions, Solutions_sorted), | |
maplist(maplist(writeln), Solutions_sorted) . | |
valid_puzzle(Ns,T) :- | |
length(Ns,6), | |
maplist(#<(0), [T|Ns]), | |
maplist(#\=(T), Ns), | |
T in 100..999, | |
Ns ins 1..10 \/ 25 \/ 50 \/ 75 \/ 100, | |
cardinality(Ns) . | |
cardinality([]). | |
cardinality([N|Ns]) :- % Small numbers can, and large numbers must, occur only once | |
N in 1..10 \/ 25 \/ 50 \/ 75 \/ 100, | |
maplist(#\=(N), Ns), | |
cardinality(Ns) . | |
cardinality([N|Ns]) :- % A small number 1–10 can also occur exactly twice | |
N in 1..10, | |
selectchk(N,Ns,Ns1), | |
maplist(#\=(N), Ns1), | |
cardinality(Ns) . | |
% Take a valid 'step' toward a solution by performing an operation on two numbers, or reaching the target number | |
step(Ns,T,['---']) :- member(T,Ns) . | |
step(Ns,T,[S|Steps]) :- | |
Ns = [_,_|_], | |
maplist(#\=(T), Ns), | |
select(A,Ns,Ns1), | |
select(B,Ns1,Ns2), | |
A #>= B, | |
operation(A,B,C,S), | |
step([C|Ns2],T,Steps) . | |
% The four permitted arithmetic operations | |
operation(A,B,C,S) :- | |
C #= A + B, | |
atomic_list_concat([A,' + ',B,' = ',C], S) . | |
operation(A,B,C,S) :- | |
A #> B, | |
C #= A - B, | |
atomic_list_concat([A,' − ',B,' = ',C], S) . | |
operation(A,B,C,S) :- | |
maplist(#<(1), [A,B]), | |
C #= A * B, | |
atomic_list_concat([A,' × ',B,' = ',C], S) . | |
operation(A,B,C,S) :- | |
maplist(#<(1), [A,B]), | |
A mod B #= 0, | |
C #= A div B, | |
atomic_list_concat([A,' ∕ ',B,' = ',C], S) . | |
% Sort the list of solutions by length | |
list_length_sorted(Lists, Lists_sorted) :- | |
map_list_to_pairs(length, Lists, Pairs0), | |
keysort(Pairs0, Pairs), | |
pairs_values(Pairs, Lists_sorted) . | |
% Remove duplicate solutions | |
dedup([],[]). | |
dedup([Solution|Solutions0],[Solution|Solutions2]) :- | |
exclude(equiv(Solution), Solutions0, Solutions1), | |
dedup(Solutions1,Solutions2) . | |
% Identify equivalent lists (those differing only in the order of elements) | |
equiv(L1,L2) :- | |
length(L1, Length), | |
length(L2, Length), | |
msort(L1, Sorted), | |
msort(L2, Sorted) . |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment