How to read data from file into Prolog

1.4k Views Asked by At

I am using SWI-Prolog to create a Wumpus World project. I am supposed to read the locations of the gold, pits, and the Wumpus from a .txt file that looks like this:

   GOLD 3 2
   WUMPUS 3 3
   PIT 2 1
   PIT 3 4

Where the words identify the object, the first number identifies the x position of the object, and the second number identifies the y position of the object. I know how to open a file and read from it, I just don't know how to tell my program that GOLD 3 2 means that the gold needs to be located at (3, 2).

3

There are 3 best solutions below

2
On BEST ANSWER

You need to open the file, read its lines, and for each one, split the line into the terms you want to use. Then, you can can place the terms in some variable or assert/1 them into the dynamic db. In the example below I assert them into the database using dynamic predicate location/3:

:- dynamic location/3.

load(File) :-
    setup_call_cleanup(
         open(File, read, Stream),
         load_loop(Stream),
         close(Stream)
         ).


load_loop(Stream) :-
    read_line_to_string(Stream, String),
    (String == end_of_file ->
         true
     ;
     split_string(String, " ", " ", [ItemS, XS, YS]),
     atom_string(Item, ItemS),
     term_string(X, XS),
     term_string(Y, YS),
     assertz(location(Item, X, Y)),
     load_loop(Stream)
    ).

show_locations :-
    forall(location(Item, X, Y),
           format('~p is at (~d, ~d)~n', [Item, X, Y])).

Supposing your input were in wumpus.txt, then this gives you:

bash-3.2$ swipl -l wumpus.pl
'GOLD' is at (3, 2)
'WUMPUS' is at (3, 3)
'PIT' is at (2, 1)
'PIT' is at (3, 4)
0
On

A DCG based solution

I would like to add a DCG based solution to the already existing solutions.

Advantages of DCGs

There are a few major advantage of using DCGs for this task:

  • You can easily test your parser interactively, without having to modify a separate file.
  • A sufficiently general DCG can be used to parse as well as generate test data.
  • Knowing this method may come in handy for more complex parsing tasks, which do not fit a predetermined format like CSV.

Preliminaries

The following code assumes the setting:

:- set_prolog_flag(double_quotes, chars).

I recommend this setting because it makes working with DCGs more readable.

Building block: token//1

We start with a short definition of what a token means:

token(T) -->
        alnum(L),
        token_(Ls),
        !, % single solution: longest match
        { atom_chars(T, [L|Ls]) }.

alnum(A) --> [A], { char_type(A, alnum) }.

token_([L|Ls]) --> alnum(L), token_(Ls).
token_([])     --> [].

Sample queries

Here are a few examples:

?- phrase(token(T), "GOLD").
T = 'GOLD'.

?- phrase(token(T), "2").
T = '2'.

?- phrase(token(T), "GOLD 2").
false.

The last example makes clear that whitespace cannot be part of a token.

Whitespace

We regard as whitespace the following sequences:

spaces --> [].
spaces --> space, spaces.

space --> [S], { char_type(S, space) }.

Solution

Hence, a sequence of tokens separated by whitespace is:

tokens([])     --> [].
tokens([T|Ts]) --> token(T), spaces, tokens(Ts).

And that's it!

We can now transparently apply this DCG to files, using Ulrich Neumerkel's visionary library(pio) for:

Here is wumpus.data:

$ cat wumpus.data
GOLD 3 2
WUMPUS 3 3
PIT 2 1
PIT 3 4

Using phrase_from_file/2 to apply the DCG to the file, we get:

?- phrase_from_file(tokens(Ts), 'wumpus.data').
Ts = ['GOLD', '3', '2', 'WUMPUS', '3', '3', 'PIT', '2', '1', 'PIT', '3', '4'] .

From such a list of tokens, it is easy to derive the necessary data, using for example again a DCG:

data([])     --> [].
data([D|Ds]) --> data_(D), data(Ds).

data_(gold(X,Y))   --> ['GOLD'], coords(X, Y).
data_(wumpus(X,Y)) --> ['WUMPUS'], coords(X, Y).
data_(pit(X,Y))    --> ['PIT'], coords(X, Y).

coords(X, Y) --> atom_number(X), atom_number(Y).

atom_number(N) --> [A], { atom_number(A, N) }.

We can use these DCGs together to:

  1. tokenize a file or given list of characters
  2. parse the tokens to create structured data.

Sample query:

?- phrase_from_file(tokens(Ts), 'wumpus.data'),
   phrase(data(Ds), Ts).
Ts = ['GOLD', '3', '2', 'WUMPUS', '3', '3', 'PIT', '2', '1'|...],
Ds = [gold(3, 2), wumpus(3, 3), pit(2, 1), pit(3, 4)] .

See for more information about this versatile mechanism.


1Please note that SWI-Prolog ships with an outdated version of library(pio), which does not work with double_quotes set to chars. Use the version supplied by Ulrich directly if you want to try this with SWI-Prolog.

0
On

Consider using library(csv), to get reading and parsing the file for free. I put your example in a file wumpus.txt:

$ cat wumpus.txt 
GOLD 3 2
WUMPUS 3 3
PIT 2 1
PIT 3 4

There are some code examples in the library documentation, here is a minimal example, straight from the top level:

?- use_module(library(csv)).
true.

?- csv_read_file("wumpus.txt", World, [separator(0' ), functor(location)]),
   forall(member(L, World), assertz(L)).
World = [location('GOLD', 3, 2), location('WUMPUS', 3, 3), location('PIT', 2, 1), location('PIT', 3, 4)].

Important: the way to say that the separator is a space character is to add the option location(0' ). The space after the 0' and before the closing parenthesis is significant!

Now you have a table in your database, location/3, which has the Type as the first argument and the coordinates as the second and third argument.

How you are going to use this is another question, I guess. Right now you can ask, "where do I have gold":

?- location('GOLD', X, Y).
X = 3,
Y = 2.

Or, "where do I have a pit"?

?- location('PIT', X, Y).
X = 2,
Y = 1 ;
X = 3,
Y = 4.