Search code examples
prologclpfd

Solving a puzzle with Prolog, throws error "Arguments are not sufficiently instantiated"


I'm trying to solve the following problem using logical constraints:

The packer has to place 5 crates onto a long lorry. The 5 crates contain chickens, barley, foxes, rat poison and wheat. The crates need to be arranged in a long line without any gaps between them so that:

• the chickens are separated from the foxes;

• the rat poison is not next to the barley;

• the rat poison is not next to the wheat.

Find out how may different ways there are of arranging these crates subject to these packing constraints.

This is what I have so far:

:- use_module(library(clpfd)).

position(Crates) :-
   Crates = [Chicken, Barley, Foxes, RatPoison, Wheat],
   Regions ins 1..5,
   Chicken   #\= Foxes,
   RatPoison #\= Barley,
   RatPoison #\= Wheat,
   labeling([], Regions).

It throws the error "Arguments are not sufficiently instantiated" when I try to run it.

I'm very new to Prolog so any help would be appreciated.


Solution

  • Firstly you are restricting Regions to 1..5 and than labeling it. But you want to know the possible positions for the 5 crates. So restrict and label Crates. Note that Regions is a free variable when you restrict it to values between 1 and 5 and the length of the list Regions is not restricted at all, thus the error when you try to label it. In this version, in the last goal of the predicate position/1, the list Crates is already restricted to a fixed length (=5) and to values between 1 and 5 when being labeled.

    Then you want the chicken and the foxes to not be in the same crate: Chicken #\= Foxes. But according to the task description they are in different crates anyway. You rather want them to be not in adjacent crates. The same goes for ratpoison/barley and ratpoison/wheat. Also no two crates can be in the same position: you can use all_distinct/1 form library(clpfd) for that. Putting this all together you get something like:

    :- use_module(library(clpfd)).
    
    position(Crates) :-
        Crates = [Chicken, Barley, Foxes, RatPoison, Wheat],
        Crates ins 1..5,
        all_distinct(Crates),
        not_adjacent(Chicken,Foxes),
        not_adjacent(RatPoison,Barley),
        not_adjacent(RatPoison,Wheat),
        labeling([], Crates).
    
    not_adjacent(X,Y) :-
        X #\= Y+1,
        Y #\= X+1.
    

    Now try to query position/1:

       ?- position(Crates).
    Crates = [1,2,4,5,3] ? ;
    Crates = [1,3,4,5,2] ? ;
    Crates = [1,4,3,2,5] ?
    ...
    

    If you don't want to go through all solutions interactively you can use findall/3 and length/2 to show all solutions and to count them:

       ?- findall(Crates,position(Crates),L),length(L,X).
    L = [[1,2,4,5,3],[1,3,4,5,2],[1,4,3,2,5],[1,5,3,2,4],[2,1,4,3,5],[2,1,4,5,3],[2,3,4,1,5],[2,3,4,5,1],[2,3,5,1,4],[2,4,5,1,3],[2,5,4,1,3],[2,5,4,3,1],[3,1,5,4,2],[3,2,5,4,1],[3,4,1,2,5],[3,5,1,2,4],[4,1,2,3,5],[4,1,2,5,3],[4,2,1,5,3],[4,3,1,5,2],[4,3,2,1,5],[4,3,2,5,1],[4,5,2,1,3],[4,5,2,3,1],[5,1,3,4,2],[5,2,3,4,1],[5,3,2,1,4],[5,4,2,1,3]],
    X = 28