How to make a random list of a specific length wit

2019-07-23 22:12发布

问题:

I'm trying to write a predicate randomnames/1 that generates a random list of three different names. The names are in a database and I already have a predicate for one random name:

name(1, Mary).
name(2, Pat).
name(3, James).
name(4, Bob).
name(5, Susan).

random_name(Name):- 
  random(0, 6, N),    
  name(N, Name). 

To get this in a list, someone suggested I should do:

random_names([A,B,C]) :-
    random_name(A),
    random_name(B),
    random_name(C).

The only problem with this is that it's possible to get duplicates in the list generated. I can't figure out how to fix that issue. I could write a new predicate for removing duplicates, but how would I substitute the duplicate with another variable then, so that the list still has three elements? And how would I even write the remove predicate when there isn't a clear head and tail in the random_names predicate?

回答1:

When programming in Prolog, think in terms of conditions that your solutions must satisfy.

Currently, you have already figured out how to describe a list with three elements, where each element is a random name.

That's a good start, but not yet sufficient: In addition, you now want to describe the condition that the elements are pairwise distinct.

So, think about how to describe a list of three elements where all elements are pairwise distinct.

I give you a start, using dif/2 to express disequality of terms in a sound way (see prolog-dif):

three_distinct_elements([A,B,C]) :-
        dif(A, B),
        dif(A, C),
        dif(B, C).

You may find a more general and more elegant way to describe this for lists with more than 3 elements. However, the above suffices to solve the task at hand.

So, it only remains to combine the predicates you already have, using for example:

three_distinct_random_names(Ls) :-
        random_names(Ls),
        three_distinct_elements(Ls).

This is simply the conjunction of conditions which you have already implemented. In total, solutions of this predicate will give you what you want: A list with three distinct random names.

However, the predicate may also fail (Exercise: Why?).

To try the predicate until it finds a solution, use for example repeat/0:

?- repeat, three_distinct_random_names(Ls).

There are also better ways to solve this. However, as a first approximation, I recommend to focus on good building-blocks, describing the conditions you want to satisfy.

I have a general comment on what you write:

I could write a new predicate for removing duplicates, but how would I substitute the duplicate with another variable then, so that the list still has three elements?

This is all worded very imperatively: You think here about "removing", "substituting" etc. To get the most out of Prolog, focus on describing the conditions that must hold for the solutions you want to find!

You want to find a list without duplicates? Describe what such a a list must look like. You want random names? Describe what such a name looks like, etc.



回答2:

In case if you are using Swi-Prolog you can use very handy randseq/3 predicate which comes bundled with Swi. randseq/3 generates list of all distinct random numbers in range from 1 to N. After getting this list generated all that remains is mapping numbers to names:

name(1, 'Mary').
name(2, 'Pat').
name(3, 'James').
name(4, 'Bob').
name(5, 'Susan').

random_names(Names, Count) :-
    % 5 is the amount of names available in database
    randseq(Count, 5, L),
    maplist(name, L, Names).

Usage examples:

?- random_names(Names, 3).
Names = ['Mary', 'James', 'Susan'].

?- random_names(Names, 5).
Names = ['Susan', 'Bob', 'James', 'Mary', 'Pat'].


标签: prolog