Trying to print output instead of true

Hi

Does anyone know how to print this output to the screen instead of getting true when I run ‘test.’ I am new to Prolog, so I have never printed anything to the console. This is part of my Programming languages assignment, but seems like I can not demo my result because of this.

%import SWI-Prolog's csv_read_file_row/3
:- use_module(library(csv)).
test :- augment('HW2data.csv').


% function that reads rows from a csv file and stores it in object
% file structure: line_number,ID,Name,HW1,HW2,HW3,Midterm,Final
% extracts data from csv file and calculates score for each student
% Score = HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4 -> round to nearest integer 
% Calculate the grade of each student
% Grade  =  (A+ = 90-100, A = 85-89, A- = 80-84, B+ = 77-79, B = 73-76, B- = 70-72, C+ = 67-69, C= 63-66, C- = 60-62, D = 50-59, E = 0-49)
% Print the score and grade of each student
% print format: StudentID    Name    Score   Grade
% Output: StudentID,    Name,    Score,   Grade

augment(FileName) :-
	csv_read_file(FileName, Rows, []),
	findall(Student, student(Student, Rows), Students),
	print_students(Students).

student(Student, Rows) :-
	nth1(LineNumber, Rows, [ID, Name, HW1, HW2, HW3, Midterm, Final]),
	nth1(LineNumber, Rows, [ID, Name, HW1, HW2, HW3, Midterm, Final]),
	calculate_score(LineNumber, ID, Name, HW1, HW2, HW3, Midterm, Final, Score, Grade),
	Student = [LineNumber, ID, Name, Score, Grade].

print_students([]).
print_students([Student | Students]) :-
	print_student(Student),
	print_students(Students).

print_student([LineNumber, ID, Name, Score, Grade]) :-
	format('~w,~w,~w,~w,~w~n', [ID, Name, Score, Grade]).

% Calculate the score of a student and store it in the object
calculate_score(LineNumber, ID, Name, HW1, HW2, HW3, Midterm, Final, Score, Grade) :-
	Score is round((HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4)),
	calculate_grade(Score, Grade).

% Calculate the grade of a student and store it in the object
calculate_grade(Score, Grade) :-
	(Score >= 90 -> Grade = 'A+';
	Score >= 85 -> Grade = 'A';
	Score >= 80 -> Grade = 'A-';
	Score >= 77 -> Grade = 'B+';
	Score >= 73 -> Grade = 'B';
	Score >= 70 -> Grade = 'B-';
	Score >= 67 -> Grade = 'C+';
	Score >= 63 -> Grade = 'C';
    Score >= 60 -> Grade = 'C-';
    Score >= 50 -> Grade = 'D';
    Grade = 'E').

You could run your program like this (for details on this, see Command Line Options):

swipl -g test -t halt my_program.pl

Or you could use the initialization/2 directive.

PS: Your code outputs a few “Singleton variable” warnings. This probably indicate a bug.

Thank you @peter.ludemann. That is a step forward. I am at least getting the errors on the console now, and your prediction seems to be right. I need to figure out how to deal with that now.

Let me also read up on the initialization/2 directive. I very knew to the programming language. :laughing:

Printing is easy, just use format/1 or format/2

For example,

?- X= 123, format("Hello world ~w~n", [X]).
Hello world 123
X = 123.

which is like printf in C language.

You program return true (and no other output) probably because the code didn’t walk to the format lines. (e.g. the argument of print_students is []).

Thank you. I am jot sure I understand though. Looking at my code, is there a specific line I can change. I am very new to the language. I was required to write the same code for 5 programming languages, and this one is the one i am finding difficulties in. It is my forst time using it, and I have run out of ideas.

Hi Peter

I have made progress. This is how my code looks like now

%This is a prolog version of _410921334_hw2.py
% read data from file HW2data.csv and calculate the score of each student
% read data from file HW2data.csv row by row
% first row is the headings
% file structure: line_number,ID,Name,HW1,HW2,HW3,Midterm,Final
% round each grade to it's nearest whole number then,
% calculate the score of each student
% Score = HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4 -> round to nearest integer
% print the score of each student
% also print the grade for each student
% print format: StudentID    Name    Score   Grade
% A+ = 90-100, A = 85-89, A- = 80-84, B+ = 77-79, B = 73-76, B- = 70-72, C+ = 67-69, C= 63-66, C- = 60-62, D = 50-59, E = 0-49

%import SWI-Prolog's csv_read_file_row/3
:- use_module(library(csv)).
test :- augment('HW2data.csv', 'test.out.csv').
 
% augment( +InFileName, +OutFileName) 
% read data from InFileName 
% calculate score and grade for each student
% write the result to OutFileName
augment(InFileName, OutFileName) :-
    csv_read_file(InFileName, Rows, []),
    open(OutFileName, write, OutStream),
    write(OutStream, 'StudentID,Name,Score,Grade'), nl(OutStream),
    augment(Rows, OutStream),
    close(OutStream).

  % augment( +Rows, +OutStream)
  % calculate score and grade for each student
  % write the result to OutStream
augment([], _).
augment([Row|Rows], OutStream) :-
    Row = [_, ID, Name, HW1, HW2, HW3, Midterm, Final],
    Score is round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4),
    Grade is grade(Score, Grade),
    write(OutStream, ID), write(OutStream, ', '),
    write(OutStream, Name), write(OutStream, ', '),
    write(OutStream, Score), write(OutStream, ', '),
    write(OutStream, Grade), nl(OutStream),
    augment(Rows, OutStream).
  
% grade( +Score)
% return the grade for the score
grade(Score, Grade) :-
    Score >= 90, !, Grade = 'A+'.
grade(Score, Grade) :-
    Score >= 85, !, Grade = 'A'.
grade(Score, Grade) :-
    Score >= 80, !, Grade = 'A-'.
grade(Score, Grade) :-
    Score >= 77, !, Grade = 'B+'.
grade(Score, Grade) :-
    Score >= 73, !, Grade = 'B'.
grade(Score, Grade) :-
    Score >= 70, !, Grade = 'B-'.
grade(Score, Grade) :-
    Score >= 67, !, Grade = 'C+'.
grade(Score, Grade) :-
    Score >= 63, !, Grade = 'C'.
grade(Score, Grade) :-
    Score >= 60, !, Grade = 'C-'.
grade(Score, Grade) :-
    Score >= 50, !, Grade = 'D'.
grade(Score, Grade) :-
    Score < 50, !, Grade = 'E'.
grade(_, Grade) :-
    Grade = 'Error'.

% test




  

% addrow( +Row, -Out)
addrow(row(ID, Name, HW1, HW2, HW3, Midterm, Final), Out) :-
    % calculate the score of each student
    Score is round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4),
    % calculate the grade of each student
    (Score >= 90 -> Grade = 'A+'
    ; Score >= 85 -> Grade = 'A'
    ; Score >= 80 -> Grade = 'A-'
    ; Score >= 77 -> Grade = 'B+'
    ; Score >= 73 -> Grade = 'B'
    ; Score >= 70 -> Grade = 'B-'
    ; Score >= 67 -> Grade = 'C+'
    ; Score >= 63 -> Grade = 'C'
    ; Score >= 60 -> Grade = 'C-'
    ; Score >= 50 -> Grade = 'D'
    ; Grade = 'E'
    ),
    % print the score of each student
    Out = [ID, Name, Score, Grade].

% test
% ?- test.

and this is the error I get, which shows that at least I am reading the file:

and the only thing printing to the output file is this:

When you read the CSV file, the result was a list like this:
[row('NO', 'ID', 'Name', 'HW1', ...), row(...), ...].

So, when you gave that to augment([Row|Rows], OutStream), you unified Row with row('NO', 'ID', 'Name', 'HW1', ...), and then tried to unify that with [_, ID, Name, HW1, HW2, HW3, Midterm, Final] and of course that didn’t work, so backtracking got you into a different attempt to read the CSV file, and that resulted in the weird error message.

Instead, you should write augment something like this:

augment([row(_, ID, Name, HW1, HW2, HW3, Midterm, Final)|Rows], OutStream) :-
    Score is round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4),
    ...

Also, you’ll need to skip the header line. The easiest way of doing this is:

csv_read_file_FileName, [_|Rows], [])
2 Likes

Thank you for your kind and informative responses Peter. Much appreciated. Let me try and implement your suggestions.

The CSV file I am trying to read contains this data:
NO,ID,Name,HW1,HW2,HW3,Midterm,Final
1,410021001,Alan,90,84.5,117,60,66
2,410021002,Bob,85,49,80,57,64
3,410021003,Carrie,90,110.5,117,68,62
4,410021004,David,117,85,0,44,55
5,410021005,Ethan,85,56,50,57,67
6,410021006,Frank,90,65,65,72,66
7,410021007,Gary,117,110.5,65,69,43
8,410021008,Helen,117,65,50,43,54
9,410021009,Igor,63,59.5,50,51,75
10,410021010,Jeff,117,110.5,117,53,75

After changing the code to this:

augment(InFileName, OutFileName) :-
    csv_read_file(InFileName, [_|Rows], []),
    open(OutFileName, write, OutStream),
    write(OutStream, 'StudentID,Name,Score,Grade'), nl(OutStream),
    augment(Rows, OutStream),
    close(OutStream).

% augment( +Rows, +OutStream)
% calculate score and grade for each student
% write the result to OutStream
augment([], _).
augment([row(_, ID, Name, HW1, HW2, HW3, Midterm, Final)|Rows], OutStream) :-
    Score is round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4),
    Grade is grade(Score),
    write(OutStream, ID), write(OutStream, ','),
    write(OutStream, Name), write(OutStream, ','),
    write(OutStream, Score), write(OutStream, ','),
    write(OutStream, Grade), nl(OutStream),
    augment(Rows, OutStream).

I am getting a similar (file_name_extension/3: Type error:) error, now starting from row one, and skipping the headers. It is complaining that it found a compound rather than the type it expected

Try gtrace/0

Try renaming your first “augment” clause, e.g. to:

test(InFileName, OutFileName) :-
    csv_read_file(InFileName, [_|Rows], []),
    open(OutFileName, write, OutStream),
        ...

Now, if you run test('HW2data.csv', 'HW2data.out'), you’ll get an error message: Arithmetic: 'grade/1' is not a function.

You appear to be trying to call a function grade. But that’s not how is/2 works – it’s only for doing arithmetic (the list of allowable operations is here).

You’ve already written calculate_grade/2, so you can get the grade by changing Grade is grade(Score) to:

calculate_grade(Score, Grade),

Thank you @peter.ludemann

Finally got it by implementing all your suggestions. Nothing more and nothing less. You make the prolog community attractive. I wouldn’t mind learning more of the language because of the support I received from you, although that is probably not the best evaluation for a whole community. :laughing:

Result:
StudentID,Name,Score,Grade
410021001,Alan,74,B
410021002,Bob,64,C
410021003,Carrie,77,B+
410021004,David,55,D
410021005,Ethan,63,C
410021006,Frank,70,B-
410021007,Gary,67,C+
410021008,Helen,58,D
410021009,Igor,63,C
410021010,Jeff,80,A-

final code:

%This is a prolog version of _410921334_hw2.py
% read data from file HW2data.csv and calculate the score of each student
% read data from file HW2data.csv row by row
% first row is the headings
% file structure: line_number,ID,Name,HW1,HW2,HW3,Midterm,Final
% round each grade to it's nearest whole number then,
% calculate the score of each student
% Score = HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4 -> round to nearest integer
% print the score of each student
% also print the grade for each student
% print format: StudentID    Name    Score   Grade
% A+ = 90-100, A = 85-89, A- = 80-84, B+ = 77-79, B = 73-76, B- = 70-72, C+ = 67-69, C= 63-66, C- = 60-62, D = 50-59, E = 0-49

%import SWI-Prolog's csv_read_file_row/3
:- use_module(library(csv)).
test :- testAugment('HW2data.csv', 'test.out.csv').
 
% augment( +InFileName, +OutFileName) 
% read data from InFileName 
% calculate score and grade for each student
% write the result to OutFileName
testAugment(InFileName, OutFileName) :-
    csv_read_file(InFileName, [_|Rows], []),
    open(OutFileName, write, OutStream),
    write(OutStream, 'StudentID,Name,Score,Grade'), nl(OutStream),
    augment(Rows, OutStream),
    close(OutStream).    

% augment( +Rows, +OutStream)
% calculate score and grade for each student
% write the result to OutStream
augment([], _).
augment([row(_, ID, Name, HW1, HW2, HW3, Midterm, Final)|Rows], OutStream) :-
    Score is round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4),
    %call calculate_grade, and assign it to Grade
    calculate_grade(Score, Grade),
    write(OutStream, ID), write(OutStream, ','),
    write(OutStream, Name), write(OutStream, ','),
    write(OutStream, Score), write(OutStream, ','),
    write(OutStream, Grade), nl(OutStream),
    augment(Rows, OutStream).  
  
% grade( +Score, Grade)
% return the grade for the score
calculate_grade(Score, Grade) :-
    Score >= 90, !, Grade = 'A+'.
calculate_grade(Score, Grade) :-
    Score >= 85, !, Grade = 'A'.
calculate_grade(Score, Grade) :-
    Score >= 80, !, Grade = 'A-'.
calculate_grade(Score, Grade) :-
    Score >= 77, !, Grade = 'B+'.
calculate_grade(Score, Grade) :-
    Score >= 73, !, Grade = 'B'.
calculate_grade(Score, Grade) :-
    Score >= 70, !, Grade = 'B-'.
calculate_grade(Score, Grade) :-
    Score >= 67, !, Grade = 'C+'.
calculate_grade(Score, Grade) :-
    Score >= 63, !, Grade = 'C'.
calculate_grade(Score, Grade) :-
    Score >= 60, !, Grade = 'C-'.
calculate_grade(Score, Grade) :-
    Score >= 50, !, Grade = 'D'.
calculate_grade(Score, Grade) :-
    Score < 50, !, Grade = 'E'.
calculate_grade(_, Grade) :-
    Grade = 'Error'.

% test
% ?- test.

Below is a bit shorter solution. A notable advantage is that it doesn’t read the whole CSV data into memory and thus can work with arbitrary large input files. Second is to use csv_write_stream/3, so we get all dirty corners of CSV writing correct and finally do the mapping from score to grade based on a table to make the mapping easier to read. Enjoy.

:- use_module(library(csv)).

testAugment(InFileName, OutFileName) :-
    setup_call_cleanup(
        open(OutFileName, write, OutStream),
        forall(csv_read_file_row(InFileName, Row, [line(Line)]),
               augment(Row, Line, OutStream)),
        close(OutStream)).

augment(_, 1, OutStream) :-
    !,
    csv_write_stream(OutStream,
                     [ row('StudentID','Name','Score','Grade') ],
                     []).
augment(row(_, ID, Name, HW1, HW2, HW3, Midterm, Final), _, OutStream) :-
    Score is round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4),
    grade(Score, Grade),
    csv_write_stream(OutStream,
                     [ row(ID, Name, Score, Grade) ],
                     []).

grade(Score, Grade) :-
    (   grade_min_score(Grade0, MinScore),
        Score >= MinScore
    ->  Grade = Grade0
    ;   Grade = 'E'
    ).

grade_min_score('A+', 90).
grade_min_score('A',  85).
grade_min_score('A-', 80).
grade_min_score('B+', 77).
grade_min_score('B',  73).
grade_min_score('B-', 70).
grade_min_score('C+', 67).
grade_min_score('C',  63).
grade_min_score('C-', 60).
grade_min_score('D',  50).

1 Like

So, the explanation of why you got the weird message is that you had defined augment/2 with 3 clauses and when you called augment/2, it tried to open the CSV file with the file name being the first row of CSV file … which gave a somewhat strange error message.

That’s one of the downsides of using a “dynamically typed” programming language – if you’d tried this in C++ or Java, you’d have got a compile time error message about passing a wrong type. Instead, you got a runtime error message that gave the actual data.

(I’m kicking myself for not noticing the cause – but it was pretty obvious when tracing using gtrace/0)

Anyway, your calculate_grade/2 looks a bit un-Prolog-ish, as a cascade of if-then-else. @jan gave a solution that depended on the ordering of the grade_min_score/2 facts. I would have written it to work with any ordering, and as a bonus, my code is more “logical”, and can output all the possible scord/grade combinations by forall(score_grade(Score, Grade), writeln(Score:Grade)).

score_grade(Score, Grade) :-
    score_range(Min, Max, Grade),
    Min =< Score,
    Score =< Max.

score_range(90,100,'A+').
score_range(85,89,'A').
score_range(80,84,'A-').
score_range(77,79,'B+').
score_range(73,76,'B').
score_range(70,72,'B-').
score_range(67,69,'C+').
score_range(63,66,'C').
score_range(60,62,'C-').
score_range(50,59,'D').
score_range(0,49,'E').

but it’s error prone to write the score_range/3 predicate that way, so it can be defined:

score_range(Min, Max, Grade) :-
    min_score(Min, Grade),
    ( min_score(Max0, _); Max0 = 100 ),
    Max is Max0 - 1,
    Min =< Max,
    \+ (min_score(Min2, _), Min < Min2, Min2 < Max).