S0rin
S0rin

Reputation: 1293

How to populate a list in Prolog?

Say you have the following predicate:

random_int(X/Y):-
  random(1,100,X),
  random(1,100,Y),
  X\=Y.

How can I populate a list of size n using the result of this predicate?

I tried the following code but it only populates the list if random_int(X) is true at the first attempt, i.e. it does not backtrack to try other combinations of X and Y.

findall(X,(between(1,N,_), random_int(X)),L).

Upvotes: 3

Views: 1606

Answers (4)

user1812457
user1812457

Reputation:

I guess a simple way to do it is to make a list of 1:100, and draw 100 times from it a sample of size 2, without replacement. Since this is Prolog and not R, you can instead do:

:- use_module(library(lists)).
:- use_module(library(random)).

random_pairs(Pairs) :-
    findall(X/Y,
        (   between(1, 100, _),
            randseq(2, 100, [X,Y])
        ), R).

This is available in SWI-Prolog at least, but it is free software and the source to randseq/3 is available on the web site.

And since it's better to not use findall unless strictly necessary, it would probable better to write:

random_pairs(Pairs) :-
    length(Pairs, 100),
    maplist(randseq(2, 100), Pairs).

or, if the X/Y is important,

random_pairs(Pairs) :-
    length(Pairs, 100),
    maplist(rand_couple(100), Pairs).

rand_couple(N, X/Y) :-
    randseq(2, N, [X,Y]).

TL;DR Use the available libraries

Upvotes: 2

lurker
lurker

Reputation: 58324

You could do it with findall/3:

random_list(N, L) :-
    findall(X, (between(1,N,_), random(50,100,X)), L).

Another tidy way to do this would be:

random_list(N, L) :-
    length(L, N),
    maplist(random(50, 100), L).

Which results in:

| ?- random_list(5, L).

L = [69,89,89,95,59]

yes
| ?-

In general, if you have a predicate, p(X1,X2,...,Xn,Y), and a list you want to fill with result Y using successive calls to p/(n+1), you can use length(List, Length) to set the length of your list, and then maplist(p(X1,...,Xn), List) to populate the list. Or, using the findall/3, you can do findall(X, (between(1,N,_), p(X1,...,Xn,X)), L)..


EDIT based upon the updated conditions of the question that the generated list be unique values...

The random predicates are not generators, so they don't create new random numbers on backtracking (either unique or otherwise). So this solution, likewise, will generate one list which meets the requirements, and then just succeed without generating more such lists on backtracking:

% Generate a random number X between A and B which is not in L

rand_not_in(A, B, L, X) :-
    random(A, B, X1),
    (   memberchk(X1, L)
    ->  rand_not_in(A, B, L, X)
    ;   X = X1
    ).

% Generate a list L of length N consisting of unique random numbers
%    between A and B

random_list(N, L) :-
    random_list(N, 50, 100, [], L).
random_list(N, A, B, Acc, L) :-
    N > 0,
    rand_not_in(A, B, A, X),
    N1 is N - 1,
    random_list(N1, A, B, [X|A], L).
random_list(0, _, _, L, L).

Yet another approach, in SWI Prolog, you can use randseq, which will give a random sequence in a range 1 to N. Just scale it:

random_list(N, A, B, L) :-
    A < B,
    Count is B - A + 1,
    randseq(N, Count, L1),
    Offset is A - 1,
    maplist(offset(Offset), L1, L).

offset(X, Offset, Y) :-
    Y is X + Offset.

?- random_list(5, 50, 100, L).
L = [54, 91, 90, 78, 75].

?-

Upvotes: 1

CapelliC
CapelliC

Reputation: 60034

I find this small 'application' of clpfd interesting:

?- N=10,M=12, repeat, findall(X, (between(1,N,_),random(1,M,X)), L), clpfd:all_different(L).
N = 10,
M = 12,
L = [5, 4, 6, 7, 9, 11, 2, 3, 8|...] 
.

note: M must be > N

Upvotes: 2

user27815
user27815

Reputation: 4797

 random_len([],0).
 random_len([Q|T],N)  :-
    random(1,100,Q), 
    random_len(T,X), 
    N  is  X+1.

Upvotes: 0

Related Questions