3
votes

I have a large list of smaller sublists like:

 [ [005,Chester,100],[001,Bob,99],[002,Andy,77] ]

I'm trying to make a display "function" that loops through the entire list and displays like:

 No.1: ID="005", Name="Chester", Grade=100
 No.2: ID="001", Name="Bob", Grade=99
 No.3: ID="002", Name="Andy", Grade=77

thinking in prolog is hard enough but i'm struggling with recursive thinking. any assistance is much appreciated!

4

4 Answers

5
votes

Having just recursion to handle data structures it's - I think - the main problem of Prolog.

This lack of expressiveness on data structures that we know are of appropriate shape, has spurred both extensions (like loops in B-Prolog) and languages - like Picat.

Anyway, plain Prolog it's powerful enough to solve your problem:

show_records(L) :-
    forall(nth1(N, L, E), format('No.~d:ID="~s",Name="~s",Grade="~d"~n', [N|E])).

yields

?- show_records([ ['005','Chester',100],['001','Bob',99],['002','Andy',77] ]).
No.1:ID="005",Name="Chester",Grade="100"
No.2:ID="001",Name="Bob",Grade="99"
No.3:ID="002",Name="Andy",Grade="77"
true.
1
votes

My answer is written with SWI-Prolog in mind, and detailed information on any built-in predicates I mention can be had by searching the documentation. However, I think everything I say here holds for other prominent Prolog implementations. Please correct me if I'm wrong.

In Prolog, we work with predicates rather than functions, (or, we only use functions that return truth-values). I will use the following predicate to refer to your example list in my Prolog queries:

ex([ [005,"Chester",100],[001,"Bob",99],[002,"Andy",77] ]).

For writing formatted text to the standard output stream, we'll use the the predicate format/2. It's signature is format(+Format, :Arguments), where Format is an atom including special sequences to facilitate interpolation. Escape sequences begin with the tilde, ~, and the full menu can be found in the SWI-Prolo documentation on format/2. Arguments is a list of prolog terms used to replace the sequences in Format. In this example, we'll only need ~w, which writes an argument and ~n, which writes a newline. As a general example: E.g.,

?- format('Write ~w, then ~w, followed by ~w~n~n', ['this', 'that', 'the other']).
Write this, then that, followed by the other

true.

You're representing a person as a list of the form [ID, Name, Grade], so we only need to write a predicate that will let us add the N in "No.N", and then we can use the person-list for the rest of the arguments to format/2:

display_person(N, Person) :-
    format('No.~w: ID="~w", Name="~w", Grade=~w~n', [N|Person]).

In the second argument of format/2, N is prefixed as the head of the argument list. We can test the predicate like so:

?- ex([P|Ps]), display_person(1, P).
No.1: ID="5", Name="Chester", Grade=100
P = [5, "Chester", 100],
Ps = [[1, "Bob", 99], [2, "Andy", 77]].

That takes care of displaying each individual person-list. Now we just need to iterate through your list of people and display each member. We'll need a two-place predicate in order to keep track of the increasing value of N for the 'No.N' portion of the display, but we only need a one-place predicate to pass the list to the display predicate. This requirement leads us to use the common pattern of using a front-end predicate supported by an auxiliary predicate:

display_people(People) :- display_people(1, People). %% Initializes the call to display_people/2 with the starting value 1

display_people(_, []). display_people(N, [Person|People]) :- display_person(N, Person), M is N+1, display_people(M, People).

Running a test using the list from ex/1:

?- ex(Ps), display_people(Ps).
No.1: ID="5", Name="Chester", Grade=100
No.2: ID="1", Name="Bob", Grade=99
No.3: ID="2", Name="Andy", Grade=77
Ps = [[5, "Chester", 100], [1, "Bob", 99], [2, "Andy", 77]] ;
false.

However, the pattern p([X|Xs]) :- q(X), p(Xs) is just what the maplist/n predicate is for, so we can save ourselves a bit of typing, and arguably make the declarative meaning of the program clearer by writing the following:

display_people(People) :-
    length(People, NumPeople),
    numlist(1, NumPeople, Nums),
    maplist(display_person, Nums, People).

numlist(Low, High, Nums) returns a list, Nums, with values from Low to High. In the preceding definition, maplist(display_person, Nums, People) will essentially call display_person(N, P) for each pair of N in Nums and P in People.

1
votes

This answer is only to answer aBathologist about the use of foldl. I'm copying verbatim his display_person/2 predicate:

display_person(N, Person) :-
    format('No.~w: ID="~w", Name="~w", Grade=~w~n', [N|Person]).

Then a little adapter that will increment the N in the previous predicate (and puts the arguments in the correct order):

display_person(Person,N,NextN) :-
    display_person(N,Person),
    NextN is N+1.

And use foldl:

display_people(People) :-
    foldl(display_person,People,1,_).

If

`People=[P1,P2,...,Pn]`

then foldl(display_person,People,1,Rn) is equivalent to:

display_person(People,1,R1),
display_person(People,R1,R2),
...
display_person(People,R(n-1),Rn).

The use of foldl in this fashion avoids going through the list once to get its length. But, on the other hand, you need an adapter predicate.

Anyway, better or worse in this case is not really important! foldl is just another trick that's good to know and that can sometimes make your code cleaner.

0
votes

You don't need recursion. First, write a predicate that displays each entry by using Prolog's format predicate, which is a printf-equivalent, and pass the list directly as the argument. Second, map the first predicate over the list using maplist.