Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Solving "Feed the Golorp" puzzle in Prolog

Some time ago I created a problem for Codeforces April Fools Day Contest 2014 - "Feed the Golorp": http://codeforces.com/contest/409/problem/I. Please read the problem statement on the link provided.

The problem was intended to be solved by people who don't know Prolog at all. Only 3 persons managed to solve the problem during the contest - in Java, Python and C++.

The main challenge is to understand what's need to be done. For a person with some Prolog experience it should be almost obvious that golorp's name like ?(_-_/___*__):-___>__. defines a Prolog predicate, and the task is to find minimal values of variables such that the predicates satisfied. There are some details: again, please read the problem statement.

Actually solving the problem after understanding the goal is not so trivial. Algorithmically the task is to topologically sort the variables according to constraints. Golorp's name can be up to 1024 characters long, so decently efficient algorithm is needed.

I wrote my reference solution in Python with regular expressions. But after the contest I started to wonder how to solve the problem in Prolog.

Because of the possible length of the golorp's name up to 1024 characters using just Prolog backtracking to bruteforce all the possibilities doesn't look feasible - constraint logic programming is probably needed.

If I could extract list of all variables and list of pairs of variables from the inequalities, I can solve it. For example in ECLiPSe CLP:

:- lib(ic).
solve(Vars, Ineqs, Result) :-
    Vars :: 0..9,
    ( foreach((A, B), Ineqs) do
        A #< B ),
    labeling(Vars),
    concat_string(Vars, Result).

[eclipse]: Vars = [__, ___, __, ___], Ineqs = [(__, ___)], solve(Vars, Ineqs, Result).

Vars = [0, 1, 0, 1]
__ = 0
___ = 1
Ineqs = [(0, 1)]
Result = "0101"

But I'm not sure how to get Vars = [__, ___, __, ___] and Ineqs = [(__, ___)] from ?(__+___+__-___):-___>__. without too much code. term_variables/2 loses duplicate variables. DCG?

Or is there completely different, better way to solve the puzzle in Prolog? (not necessarily in ECLiPSe CLP).

Update: couple of large examples to test:

?(_____________________*_________________________*________________________*___________________*_________________*__________________*___________________________*___*__*____________________*_________________________*_______________*____*___________*_____________*______*_____*_______________*____________*__________________*___________________________*___________________________):-_____>__,_______________<___________________,__>___________,________________________>______,_____________>______,____________________<_________________________,_________________<__________________,_____________<___,____<_________________________,______>____________,________________________>_________________________,_____<____________________,__<____________,_____________________>____________,__________________>_______________,_____>___,___________<_______________,_________________________>____,____<___________________,________________________>___________________________,____________>___________________________,_____<_______________.

Result: 3898080517870043672800

?(___*__*_____*____*_____*___*___*_____*___*___*___*__*___*_____*___*_____*____*___*____*_____*_____*____*_____*____*____*____*___*___*__*___*____*__*_____*_____*____*____*___*__*____*___*___*____*_____*_____*____*___*__*_____*____*__*_____*___*___*___*_____*____*___*_____*_____*___*___*___*____*__*_____*_____*__*___*__*__*_____*____*_____*___*__*_____*_____*__*____*___*____*_____*_____*___*___*___*_____*__*__*__*__*___*_____*__*___*___*____*_____*___*__*_____*_____*_____*_____*_____*__*__*___*___*_____*____*___*__*___*__*___*_____*__*_____*_____*_____*____*____*___*___*_____*____*____*__*__*_____*___*__*___*_____*_____):-____>_____,___>____.

Result: 2001022022202020121001011122021000112012210012001002220120022210000200010200001210022200000200221020000000022012020200000112201100020200

like image 212
Sergii Dymchenko Avatar asked Apr 21 '14 23:04

Sergii Dymchenko


2 Answers

last edit: Since brute force based answer was inappropriate, as advised, here is the library(clpfd) based solution:

:- [library(clpfd)].

feed_the_golorp_clp(G, Food) :-
    G = (?(F) :- C),
    prepare(C, P),
    term_variables(G, T),
    T ins 0..9,
    call(P),
    label(T),
    with_output_to(string(Food), yields(F)).

yields(E) :- E =.. [_,A,B] -> yields(A), yields(B) ; write(E).

prepare(C, P) :-
    compound(C),
    C =.. [O, A, B],
    member((O, Op), [(<, #<), (>, #>), ((,), (,))]),
    prepare(A, Pa),
    prepare(B, Pb),
    P =.. [Op, Pa, Pb].
prepare(C, C).

that works well on largest example, yielding "3898080517870043672800"...

Resume previous answer...

pure Prolog:

feed_the_golorp(G, F) :-
    G = (_ :- B),
    term_variables(G, F),
    maplist(food, F),
    call(B).

food(X) :- member(X, [0,1,2,3,4,5,6,7,8,9]).

easy, given your extensive explanation, but not so efficient...

?- time(feed_the_golorp(( ?(______________________/____+_______*__-_____*______-___):-__<___,___<____,____<_____,_____<______,______<_______ ), F)).
% 976,115 inferences, 0.874 CPU in 0.876 seconds (100% CPU, 1116785 Lips)
______________________ = __, __ = 0,
____ = 2,
_______ = 5,
_____ = 3,
______ = 4,
___ = 1,
F = [0, 2, 5, 0, 3, 4, 1] 
.

edit I'd like a counterexample based on variables ordering, since I feel my code could be incomplete/incorrect...

Indeed, I completely missed the concatenation part...

feed_the_golorp(G, Food) :-
    G = (?(F) :- C),
    term_variables(G, T),
    maplist(food, T),
    call(C),
    yields(F, S),
    atomic_list_concat(S, Food).

food(X) :- member(X, [0,1,2,3,4,5,6,7,8,9]).

yields(C, [C]) :- number(C).
yields(E, S) :- E =.. [_,A,B], yields(A,Ca), yields(B,Cb), append(Ca,Cb,S).

now the result is more plausible

?- time(feed_the_golorp(( ?(___*__*_____*____*_____*___*___*_____*___*___*___*__*___*_____*___*_____*____*___*____*_____*_____*____*_____*____*____*____*___*___*__*___*____*__*_____*_____*____*____*___*__*____*___*___*____*_____*_____*____*___*__*_____*____*__*_____*___*___*___*_____*____*___*_____*_____*___*___*___*____*__*_____*_____*__*___*__*__*_____*____*_____*___*__*_____*_____*__*____*___*____*_____*_____*___*___*___*_____*__*__*__*__*___*_____*__*___*___*____*_____*___*__*_____*_____*_____*_____*_____*__*__*___*___*_____*____*___*__*___*__*___*_____*__*_____*_____*_____*____*____*___*___*_____*____*____*__*__*_____*___*__*___*_____*_____):-____>_____,___>____), F)).
% 17,806 inferences, 0.009 CPU in 0.010 seconds (94% CPU, 1968536 Lips)
___ = 2,
__ = _____, _____ = 0,
____ = 1,
F = '2001022022202020121001011122021000112012210012001002220120022210000200010200001210022200000200221020000000022012020200000112201100020200' 
.

or, somewhat more compact and yielding output similar to example:

feed_the_golorp(G, Food) :-
    G = (?(F) :- C),
    term_variables(G, T),
    maplist(food, T),
    call(C),
    with_output_to(string(Food), yields(F)).

food(X) :- member(X, [0,1,2,3,4,5,6,7,8,9]).

yields(E) :- E =.. [_,A,B] -> yields(A), yields(B) ; write(E).

but, with_output_to/2 it's a SWI-Prolog only utility...

like image 102
CapelliC Avatar answered Sep 25 '22 05:09

CapelliC


This is an ECLiPSe solution that takes the Golorp description directly:

:- lib(ic).

golorp((?(Jaws):-Stomach), Food) :-
    term_vars(Jaws, Xs, []),
    Xs :: 0..9,
    call(Stomach)@ic,
    once labeling(Xs),
    concat_string(Xs, Food).

term_vars(X, [X|Vs], Vs) :- var(X).
term_vars(Xs, Vs, Vs0) :- nonvar(Xs),
    ( foreacharg(X,Xs), fromto(Vs,Vs2,Vs1,Vs0) do
        term_vars(X, Vs2, Vs1)
    ).

I've used a duplicate-preserving variant of term_variables/2, and exploited the fact that the ic constraint solver library defines constraint-versions of all the comparison predicates >/2, </2 etc. Sample run:

?- golorp((?(_-_/___*__):-___>__), Food).
___ = 1
__ = 0
Food = "0010"
Yes (0.00s cpu)
like image 30
jschimpf Avatar answered Sep 23 '22 05:09

jschimpf