0
votes

I'm very new to Prolog and I'm trying to build a script where I can generate a random string with Prolog. So far I've not had much luck.

My thought for doing this was to generate a list of characters that are required, create a random number based on the size of the list and try and pull a character from that list. This works fine but I also need to be able to concatenate onto a variable which will obviously start out as nothing. For now I am just trying to limit the size of the string to 5 characters but in the future I will want something a bit more complicated (such as between 5 - 8 characters).

Here is what I have so far:

generate :-
    Output = '',
    generate_random_string(Output, 0),
    write(Output).

generate_random_string(Output, 5) :-
    Characters = ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g'],
    random(0, 14, RandomValue),
    nth0(RandomValue, Characters, RandomCharacter),
    append(RandomCharacter, Output, Concat),
    Output = Concat.

generate_random_string(Output, CharNum) :-
    CharNum \= 5,

    Characters = ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g'],
    random(0, 14, RandomValue),
    nth0(RandomValue, Characters, RandomCharacter),
    append(RandomCharacter, Output, Concat),
    Count is CharNum + 1,
    Output = Concat,

    generate_random_string(Output, Count).

I believe this is breaking around the append call but I'm not really sure why, although I do know it is breaking on the first iteration and it gives a result of false. Any help towards this would be much appreciated.

3
Look at the documentation for append/3. append/3 operates on lists. You might want atom_concat/3.lurker

3 Answers

3
votes

Since @PauloMoura commented in the previous answer that foreach/3 may not be portable since it is not an ISO predicate, here is a better solution using maplist/2:

characters(['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g']).

generate(L,N) :-  
    length(L,N),
    characters(Char_list),
    length(Char_list, N1),
    maplist( random_char_generat(Char_list, N1), L ).

random_char_generat(Char_list, N, Char):-  random(0, N, X), nth0(X, Char_list, Char ).

The solution above is better in the terms listed below:

  • It works for any length of the generated random char list.
  • It works for any length of the available characters list.
  • Finally as you can see in the examples below it is pure and works for very general queries:

?- generate(L,5).
L = ['E', g, c, 'B', 'D'].

?- generate(L,8).
L = [g, g, f, d, 'E', a, g, b].

?- generate(L,N).
L = [],
N = 0 ;
L = [e],
N = 1 ;
L = [e, 'F'],
N = 2 ;
L = ['C', g, 'G'],
N = 3 ;
L = ['D', a, b, e],
N = 4 ;
L = ['F', 'E', 'G', f, b],
....
1
votes

So, as suggested in the comments, you should use atom_concat/3, in this way:

generate :-
    Output = 'abc',
    generate_random_string(Output,Result,0),
    write(Result).

generate_random_string(Output,Result,5):- !,
    Characters = ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g'],
    random(0, 14, RandomValue),
    nth0(RandomValue, Characters, RandomCharacter),
    atom_concat(RandomCharacter,Output,Result).

generate_random_string(Output,Result,CharNum) :-
    CharNum \= 5,

    Characters = ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g'],
    random(0, 14, RandomValue),
    nth0(RandomValue, Characters, RandomCharacter),
    atom_concat(RandomCharacter,Output,Concat),
    Count is CharNum + 1,
    generate_random_string(Concat,Result,Count).

Query:

?- generate.
feFaaCabc
true

As you can see, if you input a string and you want to append some chars to it, the program will output a string of length specified (in your case 5+1 because you start from 0) plus the lenght of the input string.

A different and modular way to do this could be the following, using random_member/2:

pick_random(_,0,L,L):- !.
pick_random(LC,I,SIn,SOut):-
    I > 0,
    random_member(R,LC),
    atom_concat(SIn,R,Concat),
    I1 is I-1,
    pick_random(LC,I1,Concat,SOut).


random_string(Len,SIn,SOut):-
    LC = ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g'],
    string_length(SIn,Ls),
    Len1 is Len - Ls,    
    pick_random(LC,Len1,SIn,SOut).

Query:

?- random_string(4,'gr',S).
S = grAd % note gr is in the string
?- random_string(4,'',S).
S = fdGb

From this starting point is quite easy to make also the lenght of the string random.

1
votes

Here is a more simple solution:

characters(['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g']).

generate(L,N) :-  
      length(L,N),
      characters(Char_list),
      N1 is N-1,
      foreach( (between(0,N1,X), random(0, 14, RandomValue), nth0(RandomValue, Char_list, Char)) , nth0(X,L,Char) ).

Note that the above solution takes as an argument an Number N and produces an N-digit random char list:

?- generate(L,5).
L = [f, e, 'C', 'B', 'A'].

?- generate(L,8).
L = [a, 'E', c, c, 'E', f, 'B', g].

?- generate(L,N).
L = [],
N = 0 ;
L = ['B'],
N = 1 ;
L = [c, 'F'],
N = 2 ;
L = ['E', g, 'C'],
N = 3 ;
L = ['C', c, 'F', 'D'],
N = 4 ;
L = [c, 'B', 'F', 'B', 'B'],
N = 5
....and goes on