0

So in the tag prolog someone wanted to solve the "the giant cat army riddle" by Dan Finkel (see video / Link for description of the puzzle).

Since I want to improve in answer set programming I hereby challenge you to solve the puzzle more efficient than me. You will find my solution as answer. I'll accept the fastest running answer (except if it's using dirty hacks).

Rules:

  • hardcoding the length of the list (or something similar) counts as dirty hack.
  • The output has to be in the predicate r/2, where it's first argument is the index of the list and the second its entry.
  • Time measured is for the first valid answer.
DuDa
  • 3,718
  • 4
  • 16
  • 36

2 Answers2

1
num(0..59).

%valid operation pairs
op(N*N,N):- N=2..7.  
% no need to add operations that start with 14
op(Ori,New):- num(Ori), New = Ori+7, num(New), Ori!=14.
op(Ori,New):- num(Ori), New = Ori+5, num(New), Ori!=14.

%iteratively create new numbers from old numbers
l(0,0).
{l(T+1,New) : op(Old,New)} = 1 :- l(T,Old), num(T+1), op(Old,_).

%no number twice
:- 2 #sum {1,T : l(T,Value)}, num(Value).

%2 before 10 before 14
%linear encoding
reached(T,10) :- l(T,10).
reached(T+1,10) :- reached(T,10), num(T+1).
:- reached(T,10), l(T,2).

:- l(T,14), l(T+1,_).

%looks nicer, but quadratic
%:- l(T2,2), l(T10,10), T10<T2.
%:- l(T14,14), l(T10,10), T14<T10.

%we must have these three numbers in the list somewhere
:- not l(_,2).
:- not l(_,10).
:- not l(_,14).

#show r(T,V) : l(T,V).
#show.

Having a slightly more ugly encoding improves grounding a lot (which was your main problem).

  1. I restricted op/2 to not start with 14, as this should be the last element in the list
  2. I do create the list iteratively, this may not be as nice, but at least for the start of the list it already removed impossible to reach values via grounding. So you will never have l(1,33) or l(2,45) etc... Also list generation stops when reaching the value 14, as no more operation is possible/needed.
  3. I also added a linear scaling version of the "before" section, although it is not really necessary for this short list (but a cool trick in general if you have long lists!) This is called "chaining".
  4. Also note that your show statement is non-trivial and does create some constraints/variables.

I hope this helps, otherwise feel free to ask such questions also on our potassco mailing list ;)

Max Ostrowski
  • 575
  • 1
  • 3
  • 15
  • Wow, thanks for the hints. Are there any more strategies to improve ASP code in general? – DuDa Mar 30 '21 at 15:48
  • Unfortunately there is no good compilation of such tricks. The important parts are: 1. try to keep the complexity of grounding low (avoid quadratic or worse comparisons whenever possible, also use these chaining techniques). 2. Avoid using ``` foo(X) :- X = #sum{...}. :- foo(X), bar(Y), X>Y. ``` for non fact atoms, directly write: ``` :- bar(Y), Y<#sum{...}. ``` 3. Use `clingo --text` to see the actual ground rules to find bottlenecks in grounding. – Max Ostrowski Mar 30 '21 at 15:50
  • Thank you. Is there a reason why you use `#sum` instead of `#count`? Also is there an advantage to use `:- 2 #sum {1,T : l(T,Value)}, num(Value).` instead of (for example) `:- l(T,Value), l(T1,Value), num(Value), T1 != T.`? – DuDa Mar 31 '21 at 09:16
  • Also could you recommend some literature/papers on the topic of ASP code efficiency? – DuDa Mar 31 '21 at 09:30
  • Unfortunately I do not know any publications about efficient encoding techniques, as they are hard to publish for scientists. The usual suspects are: https://potassco.org/doc/ https://teaching.potassco.org/ with the guide as the main source: https://github.com/potassco/guide/releases/ – Max Ostrowski Mar 31 '21 at 10:50
  • @DuDa `#sum` vs `#count`: Does not make a big difference and is mostly a matter of preference. Replacing the sum with an explicit quadratic rule is not a good idea. Clingo has a special propagator for `#sum` constraints and handles them directly. It has also an option and a heuristic when to translate such constraints to easier clauses, like you did. Also try `clingo --text ` to see the difference in the actual grounding process. – Max Ostrowski Apr 01 '21 at 09:19
0

My first attempt is to generate a permutation of numbers and force successor elements to be connected by one of the 3 operations (+5, +7 or sqrt). I predefine the operations to avoid choosing/counting problems. Testing for <60 is not necessary since the output of an operation has to be a number between 0 and 59. The generated List l/2 is forwarded to the output r/2 until the number 14 appears. I guess there is plenty of room to outrun my solution.

num(0..59).

%valid operation pairs
op(N*N,N):- N=2..7.  
op(Ori,New):- num(Ori), New = Ori+7, num(New).
op(Ori,New):- num(Ori), New = Ori+5, num(New).

%for each position one number
l(0,0).
{l(T,N):num(N)}==1:-num(T).  
{l(T,N):num(T)}==1:-num(N).

% following numbers are connected with an operation until 14
:- l(T,Ori), not op(Ori,New), l(T+1,New), l(End,14), T+1<=End.

% 2 before 10 before 14
:- l(T2,2), l(T10,10), T10<T2.
:- l(T14,14), l(T10,10), T14<T10.

% output
r(T,E):- l(T,E), l(End,14), T<=End.
#show r/2.

first Answer:

r(0,0) r(1,5) r(2,12) r(3,19) r(4,26) r(5,31) r(6,36) r(7,6) 
r(8,11) r(9,16) r(10,4) r(11,2) r(12,9) r(13,3) r(14,10) r(15,15) 
r(16,20) r(17,25) r(18,30) r(19,37) r(20,42) r(21,49) r(22,7) r(23,14)

There are multiple possible lists with different length.

DuDa
  • 3,718
  • 4
  • 16
  • 36