2

During my journey in Prolog, now I am stuck with i/o part. I was trying to read a file which is written in a specific format and create my facts with them and use these facts. But after reading several libraries and examples in stackoverflow, I could not manage to come up an answer though. For example, this is my file.

a,b,c,d,e
funct(a,[1,2,3,4,5])
funct(b,[2,4,6,8,10]
funct(c,[1,3,5,7,9])
funct(d,[1,1,2,3,5])
funct(e,[3,7,11,19,23])

funct/2 has list in terms of every element which is written in first line. I tried to get first line at the beginning then use it, but I could not manage to do it. I think it is the right way, although after trying Prolog now I cannot be so sure of myself. What I am trying to do is this:

functExp(a,a,1)
functExp(a,b,2)
functExp(a,c,3)
functExp(a,d,4)
functExp(a,e,5)
functExp(a,b,1)
functExp(b,a,2)
functExp(b,b,4)
functExp(b,c,6)...

I am using SWI-Prolog btw.

Edit : Sorry for inconvience. I had no idea that trying to use "funct" is wrong. I corrected what I expect. functExp/3 is the output I'm trying to get and its different from funct.I think I should use assert/assertz built-in function but I am still not sure what I should do.Again I'm so sorry for my misunderstanding. I'm still a newbie as you may guess, so I would be appreciated if you an bear with me.

tlbakh
  • 71
  • 2
  • 6
  • I'm a little unclear on your question. You say you're having trouble with the I/O aspect, but none of your code has any I/O predicates, and it looks like you're actually asking about how to generate funct/3 from your database of funct/2 facts. Can you elaborate? – Shon Apr 18 '13 at 22:08
  • In the description,I tried to explain the format of the file I have been trying to read and the output I expected to get. I want to learn how to generate that output from that kind of file. – tlbakh Apr 18 '13 at 22:20
  • @TigB okay. And it looks like your source file could be in the form of a valid prolog database, is that right? Or do you need to process it in the exact form you have it written here? – Shon Apr 18 '13 at 22:33
  • @TigB sorry, my second question is stupid. Didn't read your reply well enough. I was only thinking that, since the first looks so similar to a valid database, it would be a simple task to write `elements(a,b,c...).` and then throw periods at the end of the other lines. Then you could just consult/1 the file. I'm a real novice myself, but am I correct in thinking that you're real issue is parsing your source file and translating it into valid prolog? – Shon Apr 19 '13 at 00:04
  • 1
    @aBathologist I think this is a perfectly valid approach, the input he has is not valid anything, as far as I know, so "fixing" it would be the correct thing to do in this case... –  Apr 19 '13 at 06:49

3 Answers3

5

SWI-Prolog has plenty of IO builtins, combining them we can read easily your file data:

process_file :-
    open('input.txt', read, S),

    read_line_to_codes(S, Fs),
    atom_codes(Fa, Fs),
    atomic_list_concat(Elems, ',', Fa),

    repeat,
    read_line_to_codes(S, L),
    read_from_codes(L, T),
    ( T == end_of_file -> close(S)
    ; apply_line(T, Elems), fail
    ).

apply_line(T, Es) :-
    T =.. [F,K,As],
    maplist(out_line(F, K), Es, As).

out_line(F, K, E, A) :-
    T =.. [F, K, E, A],
    writeln(T).

input.txt of course contains the data you show

CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • This was my first reaction, too. It seems to me, however, that OP is misunderstanding how one can represent data in valid Prolog... –  Apr 19 '13 at 11:31
  • Yes,indeed, for a person who came from imperative languages Prolog seems a bit strange. So, thank you for bearing with me. – tlbakh Apr 21 '13 at 06:39
1

This problem will go down much easier if we break it down into a few sub-problems. Let's try to parse the file into a direct representation of the file first and then tackle loading it into the database in the shape you want.

This kind of problem is a great fit for definite clause grammars (DCGs). It's quite natural to express complex grammars in Prolog using this technique, and you get an efficient implementation based on difference lists. If you're careful you can even use them to generate output as well as parse input!

First let's get the tremendously helpful dcg/basics library in there:

:- use_module(library(dcg/basics)).

I find it easier to do grammars in a top-down fashion, so let's break the input down into parts. First we have a list of elements. Then we have some number of "funct" lines. I don't really know the semantics of what you're trying to accomplish so this is going to be probably badly named, but let's take a crack at it.

document(document(Elements, Functs)) --> 
  element_list(Elements), blanks, funct_list(Functs).

The result of parsing is going to be a structure document(E, F), where E is an element list and F is a funct list. Notice that we're using --> instead of :-. This is how you define a DCG rule. Internally, what's going to happen is Prolog will rewrite the predicate to give it two extra parameters: the "before" and "after" difference lists.

Now let's do elements first because they're simpler:

element_list([E|Rest]) --> element(E), ",", element_list(Rest).
element_list([E])      --> element(E).

If you've seen a CFG before this should be pretty intuitive. We're getting an element, then a comma, then more elements, or else just an element. Now let's define element:

element(E) --> [Code], { atom_codes(E, [Code]) }.

We can actually test these with phrase/2 right now:

?- phrase(element(X), "a").
X = a.

Good, that's the result we want. You may have to expand this definition if you're going to have more than single-character elements.

?- phrase(element_list(X), "a,b,c,d,e").
X = [a, b, c, d, e] ;
false.

So now we know what the first part of document/2 is going to look like on the way out of the parser: document([a,b,c,d,e], Functs). Looks just like the file, which is what we want. Our first task is just to bring in the file and all of its structure in a way that Prolog can work with.

Let's do the funct lists next:

funct_list([F|Rest]) --> functp(F), blanks, funct_list(Rest).
funct_list([F])      --> functp(F).

This looks just like the element list, but we're making functs instead of elements. Let's see what it's like to parse a funct:

functp(funct(E1, List)) --> 
  "funct(", element(E1), ",", whites, "[", number_list(List), "])".

The part in quotes is basically literal text. Again, you may have to refine this depending on how flexible you want to be in parsing the file, but this will work fine with the sample input you posted. Now we need a number list:

number_list([N|Rest]) --> number(N), ",", number_list(Rest).
number_list([N])      --> number(N).

Again, just like the element list. This is actually everything we need to test it out. Let's put your example text in a file called file.txt (you can use whatever you actually have) and run it through phrase_from_file/2 to parse it. Make sure you do not have a spare newline at the end of the file; we didn't handle that case. Also, you have a typo on line 3 (missing parenthesis).

?- phrase_from_file(document(D), 'file.txt').
D = document([a, b, c, d, e], 
             [funct(a, [1, 2, 3, 4, 5]), 
              funct(b, [2, 4, 6, 8, 10]), 
              funct(c, [1, 3, 5, 7|...]), 
              funct(d, [1, 1, 2|...]), 
              funct(e, [3, 7|...])]) ;
false.

Bingo, we have file parsing.

Step two is to use this to create your funct/3 structures. Let's make a predicate to handle one funct/2. It's going to need the element list to process and it's going to generate a list of its own.

do_normalize([E|Es], funct(F,[N|Ns]), [funct(F,E,N)|F3s]) :-
    do_normalize(Es, funct(F,Ns), F3s).
do_normalize([], funct(_, []), []).

Let's try it out:

?- do_normalize([a,b,c,d,e], funct(a,[1,2,3,4,5]), X).
X = [funct(a, a, 1), funct(a, b, 2), funct(a, c, 3), funct(a, d, 4), funct(a, e, 5)].

This looks pretty good so far!

Edit And we're back.

The above function is good, but we need to use it on each of the funct/2s that we have coming in from the file to produce all the funct/3s. We can do that with maplist, but we need to bridge to that from the output of the parser. We'll also need to use append/2 to take care of the fact that they're going to come back as nested lists; we want a flattened list.

normalize(document(Elements, Funct3s), Funct2s) :-
    normalize(Elements, Funct3s, NestedFunct2s),
    append(NestedFunct2s, Funct2s).

normalize(Elements, Funct3s, Funct2s) :- 
    maplist(do_normalize(Elements), Funct3s, Funct2s).

Now let's see if it works:

?- phrase_from_file(document(D), 'file.txt'), normalize(D, Normalized).
Normalized = [funct(a, a, 1), 
              funct(a, b, 2), 
              funct(a, c, 3), 
              funct(a, d, 4), 
              funct(a, e, 5), 
              funct(b, a, 2), 
              funct(b, b, 4), 
              funct(b, c, 6), 
              funct(..., ..., ...)|...] 

So we've now 2/3rds of the way there. We've successfully read the file and converted its contents into the structures we want in the database. Now we just need to put them into the database and we'll be done!

We must first tell Prolog that funct/3 is dynamic and can be modified at runtime:

:- dynamic funct/3.

We can use a forall/2 loop to run through the list and assert everything:

?- phrase_from_file(document(D), 'file.txt'), 
   normalize(D, Normalized), 
   forall(member(Fact, Normalized), assertz(Fact)).

Proof it's working:

?- funct(X, Y, Z).
X = Y, Y = a,
Z = 1 ;

X = a,
Y = b,
Z = 2 ;

X = a,
Y = c,
Z = 3 ;

X = a,
Y = d,
Z = 4 ;

X = a,
Y = e,
Z = 5 
...

Now let's just package the whole works up in a single nice predicate:

load_funct(Filename) :-
    phrase_from_file(document(D), Filename),
    normalize(D, Functs),
    forall(member(Funct, Functs), assertz(Funct)), !.

Try it:

?- load_funct('file.txt').
true.

And you're done! Came to about 23 lines.

Hope this helps, and hope you enjoy Prolog and stick with it!

Daniel Lyons
  • 22,421
  • 2
  • 50
  • 77
  • I can't help but think that this is a very general solution to a very specific, and trivial, problem. The original file is _almost_ proper Prolog, so why not just correct it and consult it? See my answer btw. –  Apr 19 '13 at 06:12
  • 1
    @Boris I agree that this is a very general solution to a very specific problem. I often felt that I lacked for good examples of how to approach problems when I was first learning Prolog, so that's my intent here: to show how I would approach a problem like this, knowing I couldn't change the input format and wanting to easily handle change in the future. – Daniel Lyons Apr 19 '13 at 14:34
  • Absolutely. I guess my point was that obviously OP is just misunderstanding the "data as Prolog facts" approach. And as your answer shows, writing a parser is never really trivial (while correcting the syntax sometimes is). –  Apr 19 '13 at 15:12
  • Not trivial, no, but it has a certain resilience that I admire. Busting out CFG parsing capabilities outside Prolog is usually a hassle. Anyway, thanks! – Daniel Lyons Apr 19 '13 at 15:19
  • I read your answer twice after I edited my question. To be honest, it is so complicated to understand at first. Maybe,my edited question is much more trivial than what I asked at first. – tlbakh Apr 21 '13 at 06:43
1

EDIT

I understand that this does not answer your exact question, but what you want to do, if I might summarize:

  • take input which looks as almost valid Prolog
  • transform it to output that looks as almost valid Prolog (you are missing the dots there, too)
  • do this in Prolog

You are not telling us what is the useful thing you are going to do with it. After all, one would expect that you are going to do something Prolog related with the functs that you want as output. Then it doesn't make sense to just have them pushed to standard output, one per line (without a dot at the end).

And here a solution:

Your original file is almost proper Prolog, so why not use a text editing program to fix it? For example, using sed, you could use:

1 s/^/atoms(\[/
1 s/$/\])/
s/$/\./

With a more complex example you might need to tweak it, but still relatively straight forward, if you already have almost valid Prolog.

(Note: there was a closing parentheses missing on line 3 of your original input file, I guess that was oversight)

Now you can use consult to load the file from Prolog. You still need something like the do_normalize that @DanielLyons showed, if you insist that you want to have all relations explicitly in the database. But look at this rule:

funct(A,B,N) :-
    funct(A,Ns), nth1(BN,Ns,N),
    atoms(Bs), nth1(BN,Bs,B).

It defines funct/3 in terms of the original (corrected) data and the example you provided. You can enumerate all "normalized" functors that you need, in the order that you showed, by just saying:

?- funct(A,B,N).

Or query it with arbitrary arguments instantiated.

You can of course assert them in the database, or write them to a file, if this is what heart desires.

  • +1. Defining `funct/3` in terms of `funct/2` is much more elegant than what I did. I suspect there's a time/memory tradeoff there, but in general removed code is debugged code. :) – Daniel Lyons Apr 19 '13 at 14:36
  • @DanielLyons As I wrote, one can run it once through and assert the solutions in the database, if necessary. –  Apr 19 '13 at 15:29