There are two ways to go through a list, the most "natural" - using recursion, which you probably already know - or a combination of member
with findall
/ bagof
/ setof
. I'll show both forms, as they have both advantages and disadvantages (mostly readability and maybe in performance).
Recursion
First, it is necessary to determine if the M
matter belongs to the C
course:
pertence_curso(M,C) :-
curriculo(C,Lista),
member(M, Lista).
Next, if you cover all the subjects of the student checking if they belong to the course or not; if it belongs, is included in the list, otherwise you "jump" it and go pro:
extra(Aluno, Curso, R) :-
historico(Aluno, Itens),
percorre_itens(Itens, Curso, R).
% Se acabaram os itens, retorna a lista vazia
percorre_itens([],_,[]).
% Se a matéria pertence ao curso, inclui ela no resultado
percorre_itens([item(CM,_,_,_,_)|Resto], Curso, [Materia|Resultado]) :-
pertence_curso(CM, Curso),
!,
materia(CM, Materia, _),
percorre_itens(Resto, Curso, Resultado).
% Se ela não pertence ao curso (cláusula anterior falhou), não inclui
percorre_itens([_|Resto], Curso, Resultado) :-
percorre_itens(Resto, Curso, Resultado).
The problem with this method is that it will return repeated results (in its example, 1
was taken in the first and second semester, so it will be included in the final result twice). To get around this, you can either use an accumulator (ie it starts with the empty list, and it inserts items into it, but only inserts if it is not already there) or do the normal method and in the end delete the repeated items. >
member and setof
The idea behind this technique is to non-deterministically choose any element that satisfies the above conditions, and to ask for "more solutions" until all possibilities are exhausted:
materia_extra(Aluno, Curso, Materia) :-
curriculo(Curso, Materias),
member(CM, Materias),
historico(Aluno, Itens),
member(item(CM,_,_,_,_), Itens),
materia(CM, Materia, _).
And that's it! I chose a subject in the list of course subjects, then chose an item in the list of subjects taken by the student whose code is the same as the subject chosen. This predicate will succeed if such a combination exists, and will return only one result (a single subject). If this combination is not found, it will fail.
It's easy to see that if you call materia_extra
on the terminal and ask for "more results" it will give you all the stuff that satisfies this condition, one at a time. But how to return all at once, in a list without repeating (i.e. a set)? Then enter setof
:
extra(Aluno, Curso, Lista) :-
setof(M, materia_extra(Aluno, Curso, M), Lista).
It will execute materia_extra/3
as many times as possible, and for each valid result, put the M
(specified in the first argument) in the Lista
list. In the case of setof
it will only do this if the element no longer exists there ( bagof
works the same, only accepts repeated elements; findall
is equal to bagof
, but does not fail if there is no result , returning an empty list instead).