Part 1 The Prolog Language Chapter 6 Input and Output 1 6.1 Communication with files The method of communication between the user and the program: User questions to the program Program answers in terms of instantiations of variables Extensions to this basic communication method are needed in the following areas: input of data in forms other than questions—for example, in the form of English sentences, output of information in any format desired, and input from and output to any computer file or device and not just the user terminal. 2 6.1 Communication with files We first consider the question of directing input and output to files. The program can read data from several input files (input streams), and output data to several output files (output streams). Data coming from the user’s terminal is treated as just another input stream. Data output to the terminal is treated as another output stream. User terminal user Input streams user file 1 file 2 file 3 file 4 Output streams 3 6.1 Communication with files At any time during the execution of a Prolog program, only two files are ‘active’: one for input (current input stream) one for output (current output stream) The current input stream can be changed to another file, Filename, by the goal: see( Filename) The current output stream can be changed by a goal of the form: tell( Filename) The goal seen closes the current input file. seen closes the current input, and resets it to user input. The goal told closes the current output file. told closes the current output, and resets it to user output. 4 6.1 Communication with files For examples: … see( file1), read_from_file( Information), see( user), … tell( file3), write_on_file( Information), tell( user), … Another example: readfile( X, Y) :- see('test.txt'), get( X), get( Y), seen. writefile(X) :- tell('test1.txt'), put( X), told. | ?- readfile(X, Y). X = 48 Y = 49 test.txt yes 0123456 … | ?- writefile(99). yes test1.txt c 5 Exercises Please write a program to open two text files: testa.txt and testb.txt, and read data from them alternately. | ?- readfile(X, Y). X = 52 Y = 56 Y from testb.txt yes X from testa.txt Please write a program to open one text file: testc.txt, and write two data. | ?- writefile(65, 66). 6 6.2 Processing files of terms 6.2.1 Read and write read( X): The built-in predicate read is used for reading terms from the current input stream. The goal read( X) will cause the next term, T, to be read, and this term will be matched with X. If X is a variable then X will become instantiated to T. If matching does not succeed then the goal read( X) fails. The predicate read is deterministic, so in the case of failure there will be no backtracking to input another term. If read( X) is executed when the end of the current input file has been reached then X will become instantiated to the atom end_of_file. readfile( X, Y) :- see('test.txt'), read( X), read( Y), seen. 7 6.2 Processing files of terms 6.2.1 Read and write write( X): The built-in predicate write outputs a term. The goal write( X) will output the term X on the current output file. X will be output in the same standard syntactic form in which Prolog normally displays values of variables. A useful feature of Prolog is that the write procedure ‘knows’ to display any term no matter how complicated it may be. For example: | ?- write("this is a test"). [116,104,105,115,32,105,115,32,97,32,116,101,115,116] | ?- write('this is a test'). this is a test yes 8 Examples | ?- read(X), write(X). 'This is a test'. This is a test X = 'This is a test' yes | ?- read(X), write(X). "This is a test". [84,104,105,115,32,105,115,32,97,32,116,101,115,116] X = [84,104,105,115,32,105,115,32,97,32,116,101,115,116] yes | ?- get(X), put(X). c c X = 99 yes 9 6.2 Processing files of terms 6.2.1 Read and write An example: cube( N, C) :C is N * N * N. | ?- cube(2, X). X=8 yes | ?- cube(5, Y). Y = 125 yes | ?- cube(12, Z). Z = 1728 yes cube :read( X), process( X). process( stop) :- !. process( N) :C is N * N * N, write( C), cube. | ?- cube. 2. 8 5. 125 12. 1728 stop. (16 ms) yes 10 Exercise cube :- read( stop), !. cube :- read( N), C is N * N * N, write( C), cube. Is the above procedure correct? Why? Would you please show some correct examples? 11 6.2 Processing files of terms 6.2.1 Read and write An incorrect simplified: cube :- read( stop), !. cube :- read( N), C is N * N * N, write( C), cube. The reason why this is wrong can be seen easily if we trace the program with input data 5. The goal read( stop) will fail when the number is read, and this number will be lost forever. The next read goal will input the next term. On the other hand, it could happen that the stop signal is read by the goal read( N), which would then cause a request to multiply non-numeric data. 12 6.2 Processing files of terms 6.2.1 Read and write tab( N): The built-in predicates for formatting the output. They insert spaces and new lines into the output stream. The goal tab( N) causes N space to be output. The predicate nl (which has no arguments) causes the start of a new line at output. cube :- read( X), process( X). process( stop) :- !. process( N) :- C is N * N * N, tab(10), write( C), nl, cube. 13 6.2 Processing files of terms 6.2.1 Read and write Another version: cube :- write(‘Next item, please: ’), read( X), process( X). process( stop) :- !. process( N) :C is N * N * N, write(‘Cube of ’), write( N), write(‘ is ’), write( C), nl, cube. | ?- cube. Next item, please: 5. Cube of 5 is 125 Next item, please: 12. Cube of 12 is 1728 Next item, please: stop. (31 ms) yes 14 6.2 Processing files of terms 6.2.2 Displaying lists writelist( L): The procedure outputs a list L so the each elements of L is written on a separate line: writelist([]). writelist([X|L]) :- write( X), nl, writelist( L). | ?- writelist( [a, b, c]). a b c yes 15 6.2 Processing files of terms 6.2.2 Displaying lists writelist2( L): If we have a list of lists, we can define the procedure: writelist2([]). writelist2([L|LL]) :- doline( L), nl, writelist2( LL). doline([]). doline([X|L]) :- write( X), tab(1), doline( L). | ?- writelist2([[a,b,c],[d,e,f],[g,h,i]]). abc def ghi (15 ms) yes 16 6.2 Processing files of terms 6.2.2 Displaying lists bars( L): A list of integer numbers can be sometimes conveniently shown as a bar graph. The procedure, bars, can be defined as: bars([]). bars([N|L]) :- stars( N), nl, bars( L). stars( N) :- N > 0, write( *), N1 is N-1, stars(N1). stars( N) :- N =<0. | ?- bars([3,4,6,5]). *** **** ****** ***** true ? yes 17 Exercise Please write a procedure square(N) to draw a square with size N. For example, | ?- square(3). *** *** *** yes 18 Exercise Please write a procedure square which can read two parameters N and S and draw a NXN square with symbol S. For example, | ?- square. Please input square size N: 4. Please input symbol S: & &&&& &&&& &&&& &&&& yes 19 Exercise Please write a procedure squares which can read two parameters N and S and draw NXN squares with symbol S. This procedure will exit if the value of N is 0. For example, | ?- square. Please input Please input GGG GGG GGG Please input Please input 88888 88888 88888 88888 88888 Please input yes square size N: 3. symbol S: G square size N: 5. symbol S: 8 square size N: 0. 20 6.2 Processing files of terms 6.2.3 Processing a file of terms processfile: A typical sequence of goals to process a whole file, F, would look something like this: …, see( F), processfile, see( user), … Here processfile is a procedure to read and process each term in F, one after another, until the end of the file is encountered. processfile :- read( Term), process( Term). process( end_of_file) :- !. process( Term) :- treat( Term), processfile. Here treat( Term) represents whatever is to be done with each term. 21 6.2 Processing files of terms 6.2.3 Processing a file of terms An example: This procedure showfile can display on the terminal each term together with its consecutive number. showfile( N) :- read( Term), show( Term, N). show( end_of_file, _) :- !. show( Term, N) :- write(N), tab( 2), write( Term), nl, N1 is N+ 1, showfile( N1). | ?- showfile(3). a. 3 a aa. 4 aa abcdefg. 5 abcdefg test. 6 test 22 Exercises Let f be a file of terms. Define a procedure findterm( Term) that displays on the terminal the first term in f that matches Term. Let f be a file of terms. Write a procedure findallterms( Term) That displays on the terminal all the terms in f that match Term. Make sure that Term is not instantiated in the process (which could prevent its match with terms that occur latter in the file). 23 Exercise Let testfile.txt be a file. Write a procedure printfile( ‘testfile.txt’) that displays on the terminal all the context in testfile.txt. printfile(FILE) :- see(FILE), process, seen. process :- read(Term), test(Term). test(end_of_file) :- !. test(Term) :- write(Term), write('.'), nl, process. processfile(FILE) :- see(FILE), process, seen. process :- get_char(Term), test(Term). test(end_of_file) :- !. test(Term):- write(Term), process. processfile(FILE) :- see(FILE), process, seen. process :- get_char(Term), test(Term). test(end_of_file) :- !. test(Term):- write(Term), process. 24 6.3 Manipulating characters A character is written on the current output stream with the goal put( C) where C is the ASCII code (0-127) of the character to be output. For example: ?- put( 65), put( 66), put( 67). would cause the following output ABC 65 is the ASCII code of ‘A’, 66 of ‘B’, 67 of ‘C’. A single character can be read from the current input stream by the goal get0( C) get0( C) causes the current character to be read from the input stream. The variable C becomes instantiated to the ASCII code of this character. get( C) get( C) is used to read non-blank characters. get( C) causes the skipping over of all non-printable characters. 25 6.3 Manipulating characters Define procedure squeeze: Squeeze (壓縮) can read a sentence from the current input stream, and output the same sentence reformatted so that multiple blanks between words are replaced by single blanks. For example: An acceptable input is then: The robot tried to pour wine out The goal squeeze would output: of the bottle. The robot tried to pour wine out of the bottle. squeeze :- get0( C), put( C), dorest( C). dorest( 46) :- !. dorest( 32) :- !, get( C), put( C), dorest( C). dorest( Letter) :- squeeze. 26 6.3 Manipulating characters squeeze :- get0( C), put( C), dorest( C). dorest( 46) :- !. dorest( 32) :- !, get( C), put( C), dorest( C). dorest( Letter) :- squeeze. | ?- squeeze. this is a test. this is a test. (15 ms) yes | ?- squeeze. a full stop , a blank or a letter a full stop , a blank or a letter . yes Here still has a blank, can you improve the program to remove this? . Ps. The ASCII code of ‘,’ is 44. 27 Exercise Given a squeeze procedure defined as follows: squeeze :- get0( C), put( C), dorest( C). dorest( 46) :- !. dorest( 32) :- !, get( C), put( C), dorest( C). dorest( Letter) :- squeeze. We can run this program and show it result. | ?- squeeze. a full stop , a blank or a letter . a full stop , a blank or a letter . yes Here still has a blank, can you improve the program to remove this? For example: a full stop, a blank or a letter. 28 6.4 Constructing and decomposing atoms name( A, L): name is a built-in predicate. name is true if L is the list of ASCII codes of the characters in A. For example: | ?- name( Z, [122, 120, 50, 51, 50]). Z = zx232 yes | ?- name( zx232, A). A = [122,120,50,51,50] Yes There are two typical uses of name: (1) given an atom, break it down into single characters. (2) given a list of characters, combine them into an atom. 29 6.4 Constructing and decomposing atoms The example of first kind of application: taxi( X) taxi( X) tests whether an atom X represents a taxi. taxi( X) :- name( X, Xlist), name( taxi, Tlist), conc( Tlist, _, Xlist). conc([],L,L). conc([X|L1],L2,[X|L3]) :- conc(L1,L2,L3). | ?- taxi( taxia1). yes | ?- taxi( taxilux). yes | ?- taxi( taxtax). no 30 6.4 Constructing and decomposing atoms taxi( X) :- name( X, Xlist), name( taxi, Tlist), conc( Tlist, _, Xlist). conc([],L,L). conc([X|L1],L2,[X|L3]) :- conc(L1,L2,L3). | ?- taxi(taxilux). 1 1 Call: taxi(taxilux) ? 2 2 Call: name(taxilux,_104) ? 2 2 Exit: name(taxilux,[116,97,120,105,108,117,120]) ? 3 2 Call: name(taxi,_143) ? 3 2 Exit: name(taxi,[116,97,120,105]) ? 4 2 Call: conc([116,97,120,105],_138,[116,97,120,105,108,117,120]) ? 5 3 Call: conc([97,120,105],_138,[97,120,105,108,117,120]) ? 6 4 Call: conc([120,105],_138,[120,105,108,117,120]) ? 7 5 Call: conc([105],_138,[105,108,117,120]) ? 8 6 Call: conc([],_138,[108,117,120]) ? 8 6 Exit: conc([],[108,117,120],[108,117,120]) ? 7 5 Exit: conc([105],[108,117,120],[105,108,117,120]) ? 6 4 Exit: conc([120,105],[108,117,120],[120,105,108,117,120]) ? 5 3 Exit: conc([97,120,105],[108,117,120],[97,120,105,108,117,120]) ? 4 2 Exit: conc([116,97,120,105],[108,117,120],[116,97,120,105,108,117,120]) ? 1 1 Exit: taxi(taxilux) ? yes {trace} 31 6.4 Constructing and decomposing atoms Another example of second kind of application: getsentence( Wordlist) getsentence reads a free-form natural language sentence and instantiates Wordlist to some internal representation of the sentence. For example: If the current input stream is: Mary was pleased to see the robot fail. The goal getsentence( Wordlist) will cause the instantiation: Sentence = [‘Mary’, was, pleased, to, see, the, robot, fail] 32 6.4 Constructing and decomposing atoms % Figure 6.2 A procedure to transform a sentence into a list of atoms. getsentence( Wordlist) :get0( Char), getrest( Char, Wordlist). getrest( 46, [] ) :- !. getrest( 32, Wordlist) :- !, getsentence( Wordlist). getrest( Letter, [Word | Wordlist] ) :getletters( Letter, Letters, Nextchar), name( Word, Letters), getrest( Nextchar, Wordlist). getletters( 46, [], 46) :- !. getletters( 32, [], 32) :- !. getletters( Let, [Let | Letters], Nextchar) :get0( Char), getletters( Char, Letters, Nextchar). 33 6.4 Constructing and decomposing atoms The procedure getsentence first reads the current input character, Char, and then supplies this character to the procedure getrest to complete the job. getsentence( Wordlist) :get0( Char), getrest( Char, Wordlist). | ?- getsentence(X). Mary was pleased to see the robot fail. X = ['Mary',was,pleased,to,see,the,robot,fail] yes | ?- getsentence(X). test a file. X = [test,a,file] yes 34 6.4 Constructing and decomposing atoms getrest has to react properly according to three cases: (1) (2) (3) Char is the full stop: then everything has been read. Char is the blank: ignore it, getsentence form rest of input. Char is a letter: first read the word, Word, which begins with Char, and the use getrest to read the rest of the sentence, producing Wordlist. The cumulative result is the list [Word|Wordlist]. getrest( 46, [] ) :- !. getrest( 32, Wordlist) :- !, getsentence( Wordlist). getrest( Letter, [Word | Wordlist] ) :getletters( Letter, Letters, Nextchar), name( Word, Letters), getrest( Nextchar, Wordlist). | ?- name( t, T). T = [116] | ?- name( a, T). T = [97] | ?- getrest(116, X). est a file. X = [test,a,file] yes | ?- getrest(97, Y). file. Y = [a,file] (16 ms) yes 35 6.4 Constructing and decomposing atoms The procedure that reads the characters of one word is: getletters( Letter, Letters, Nextchar) The three arguments are: (1) Letter is the current letter (already read) of the word being read. (2) Letters is the list of letters (starting with Letter) of up to the end of the word. (3) Nextchar is the input character that immediately follows the word read. Nextchar must be a non-letter character. getletters( 46, [], 46) :- !. getletters( 32, [], 32) :- !. getletters( Let, [Let | Letters], Nextchar) :get0( Char), getletters( Char, Letters, Nextchar). 36 6.4 Constructing and decomposing atoms getletters( 46, [], 46) :- !. getletters( 32, [], 32) :- !. getletters( Let, [Let | Letters], Nextchar) :get0( Char), getletters( Char, Letters, Nextchar). | ?- getletters(116, X, Y). est. X = [116,101,115,116] Y = 46 yes | ?- getletters(116, X, Y). est X = [116,101,115,116] Y = 32 yes | ?- getletters(116, X, Y). 1 1 Call: getletters(116,_42,_43) ? 2 2 Call: get0(_123) ? e 2 2 Exit: get0(101) ? 3 2 Call: getletters(101,_85,_43) ? 4 3 Call: get0(_174) ? s 4 3 Exit: get0(115) ? 5 3 Call: getletters(115,_136,_43) ? 6 4 Call: get0(_225) ? t 6 4 Exit: get0(116) ? 7 4 Call: getletters(116,_187,_43) ? 8 5 Call: get0(_276) ? . 8 5 Exit: get0(46) ? 9 5 Call: getletters(46,_238,_43) ? 9 5 Exit: getletters(46,[],46) ? 7 4 Exit: getletters(116,[116],46) ? 5 3 Exit: getletters(115,[115,116],46) ? 3 2 Exit: getletters(101,[101,115,116],46) ? 1 1 Exit: getletters(116,[116,101,115,116],46) ? X = [116,101,115,116] Y = 46 (78 ms) yes 37 6.4 Constructing and decomposing atoms | ?- getsentence(X). test and test. X = [test,and,test] yes | ?- getsentence(X). test. X = [test] yes | ?- getsentence([X]). test. X = test yes | ?- getsentence([X]). test and test. (16 ms) no uncaught exception: error(syntax_error('user_input:43 (char:10) . or operator expected after expression'),read_term/3) 38 Exercise Define the relation starts( Atom, Character) to check whether Atom starts with Character. Please update the program getsentence to process the comma(,)(44) and semicolon(;)(59) symbols. For example: | ?- getsentence(X). That is a test; this is a test, too. X = ['That', is, a, test, this, is, a, test, too] 39 6.5 Reading programs consult( F): We can tell Prolog to read a program from a file F. For example: |?- consult( program3). All the clauses in file program3 are read and loaded into the memory. They will be used by Prolog when answering further questions from the user. If another file is ‘consulted’ at some later time during the same session, then the clauses from this new file are added into the memory. However, details depend on the implementation and other circumstances. 40 6.5 Reading programs If the new file contains clauses about a procedure defined in the previously consulted file, then the new clauses may be simply added at the end of the current set of clauses, or The previous definition of this procedure may be entirely replaced by the new one. Several files may be consulted by the same consult goal. For example: |?- consult([program3, program4, queens]). Consulted programs are used by a Prolog interpreter. | ?- consult('C:/GNU-Prolog/Prologcode/programs/fig1_8.pl'). compiling C:\GNU-Prolog\Prologcode\programs\fig1_8.pl for byte code... C:\GNU-Prolog\Prologcode\programs\fig1_8.pl compiled, 42 lines read - 2851 bytes written, 31 ms yes 41