How can I quickly save and load data files in Prolog, for use with games?
Save files with save(File_name,Term), where File_name is the file name in folder folder_name and Term is a compound containing the data file.
?- save("file_name.txt",[[["player_name","Harold"],["score",10],["symbol",key_word]],[["player_name","Queenie"],["score",20],["symbol",schema]]]).
Load files with load(File_name,Term), where File_name is the file name in folder folder_name and Term is the contents loaded from the file.
?- load("file_name.txt",Term),writeln1(Term).
Term=[[["player_name","Harold"],["score",10],["symbol",key_word]],[["player_name","Queenie"],["score",20],["symbol",schema]]]
If you only want to save a single term, a basic version of what you want is the following:
save(Filename, Term) :-
open(Filename, write, Stream),
write_canonical(Stream, Term),
write(Stream, '.'),
close(Stream).
load(Filename, Term) :-
open(Filename, read, Stream),
read(Stream, Term),
close(Stream).
For example:
?- save('hello.term', hello(world)).
true.
At this point the file hello.term (the name is arbitrary) contains this:
hello(world).
Then:
?- load('hello.term', Term).
Term = hello(world).
Notes:
The read predicates expect to read a term with a terminating period (.), but the write predicates don't write those out by themselves. Therefore save/2 needs to write it explicitly.
There is no error handling here.
There is also a write/2 predicate that could be used. The difference is relevant if you use operators: The term a + b will be written as a + b by write and as +(a, b) by read. The latter can be read correctly even in cases where the write and the read happen with different operator declarations.
More work is needed if you want to write or read multiple terms.
Using the code below you can save and load data files in Prolog.
% save("file_name.txt",[[["player_name","Harold"],["score",10],["symbol",key_word]],[["player_name","Queenie"],["score",20],["symbol",schema]]]).
save(File_name,Term) :-
string_concat("folder_name/",File_name,Path),
term_to_atom(Term,Atom),
string_atom(String,Atom),
(open_s(Path,write,Stream1),
write(Stream1,String),
close(Stream1)),!.
% load("file_name.txt",Term).
load(File_name,Term) :-
File_name="file_name.txt",
string_concat("folder_name/",File_name,Path),
phrase_from_file_s(string(Codes),Path),
string_codes(String,Codes),
atom_to_term(String,Term,[]).
open_s(File,Mode,Stream) :-
atom_string(File1,File),
open(File1,Mode,Stream),!.
string_atom(String,Atom) :-
atom_string(Atom,String),!.
phrase_from_file_s(string(Output), String) :-
atom_string(String1,String),
phrase_from_file(string(Output), String1),!.
string(String) --> list(String).
list([]) --> [].
list([L|Ls]) --> [L], list(Ls).
writeln1(Term) :-
term_to_atom(Term,Atom),
writeln(Atom),!.
See also string to list (Test 15) in List Prolog, which does the same thing as atom_to_term//3. The predicates above replace the need for using long grammars, however I have posted answers on My long SWI-Prolog grammar keeps on failing and I can't get my Prolog DCG working with atom concat for those who are interested.
Related
I'm currently working on a Prolog program which would, logically, have some kind of "save/load" feature. I've gotten the save part to work, where I'm now (as a start) creating three *.txt files, which will contain a 2D array/list each. However, I'm facing some issues trying to load it back into the program.
What I have right now is something as simple as:
% Initialize globals
?- nb_setval(aisles_global, []).
% Load all previously saved data from the given .txt files
load_all():-
exists_file('C:\\Users\\Xariez\\Desktop\\aisles.txt'),
open('C:\\Users\\Xariez\\Desktop\\aisles.txt', read, InAisles),
read_line_to_codes(InAisles, AisleString),
% read_line_to_string(InAisles, AisleString),
writeln(AisleString),
nb_setval(aisles_global, AisleString),
close(InAisles).
As previously mentioned, the files will have a 2D array each, but as an example:
aisles.txt
[["Beer", "Cider" ], [ "Milk", "Juice" ], ["Light Bread", "Dark Bread"]]
I've tried using both read_line_to_codes/2 and read_line_to_string/2. While it technically works when reading it into codes, I feel like it would quickly become annoying to reconstruct a 2D list/array since it's now got every character as a code. And while reading into a string succeeds in the reading part, we now have a string that LOOKS like a list, but isn't really one (if I've understood this situation correctly?). And hence I'm here.
If anyone got any ideas/help, that'd be greatly appreciated. Thanks!
Prolog has predicates for doing input/output of terms directly. You don't need to roll these yourself. Reading terms is done using read, while for writing there are several options.
Your best shot for writing is probably write_canonical, which will write terms in "canonical" syntax. This means that everything is quoted as needed (for example, an atom 'A' will be printed as 'A' and not as plain A like write would print it), and terms with operators are printed in prefix syntax, which means you get the same term even if the reader doesn't have the same operators declared (for example, x is y is printed as is(x, y)).
So you can write your output like:
dump(Aisles, Filename) :-
open(Filename, write, OutAisles),
write_canonical(OutAisles, Aisles),
write(OutAisles, '.'),
close(OutAisles).
Writing the . is necessary because read expects to read a term terminated by a period. Your reading predicate could be:
load(Aisles, Filename) :-
open(Filename, read, InAisles),
read(InAisles, Aisles),
close(InAisles).
Running this using some example data:
?- aisles(As), dump(As, aisles).
As = [["Beer", "Cider"], x is y, 'A', _G1380, ["Milk", "Juice"], ["Light Bread", "Dark Bread"]].
?- load(As, aisles).
As = [["Beer", "Cider"], x is y, 'A', _G1338, ["Milk", "Juice"], ["Light Bread", "Dark Bread"]].
The contents of the file, as you can check in a text editor, is:
[["Beer","Cider"],is(x,y),'A',_,["Milk","Juice"],["Light Bread","Dark Bread"]].
Note the canonical syntax for is. You should almost certainly avoid writing variables, but this shouldn't be a problem in your case.
Let me explain clearly.
The following is my requirement:
Let's say there is a command which has an option specified as '-f' that takes a filename as argument.
Now I have 5 files and I want to create a new file merging those 5 files and give the new filename as argument for the above command.
But there is a difference between
reading a single file and
merging all files & reading the merged file.
There is more IO (read from 5 files + write to the merged file + any IO our command does with the given file) generated in the second case than IO (any IO our command does with the given file) generated in the first case.
Can we reduce this unwanted IO?
In the end, I really don't want the merged file at all. I only create this merged file just to let the command read the merged files content.
And to say, I also don't want this implementation. The file sizes are not so big and it is okay to have that extra negligible IO. But, I am just curious to know if this can be done.
So in order to implement this, I have following understanding/questions:
Generally what all the commands (that takes the filename argument) does is it reads the file.
In our case, the filename(filepath) is not ready, it's just an virtual/imaginary filename that exists (as the mergation of all files).
So, can we create such virtual filename?
What is a filename? It's an indirect inode entry for a storage location.
In our case, the individual files have different inode entries and all inode entries have different storage locations. And our virtual/imaginary file has in fact no inode and even if we could create an imaginary inode, that can only point to a storage in memory (as there is no reference to the storage location of another file from a storage location of one file in disk)
But, let's say using advanced programming, we are able to create an imaginary filepath with imaginary inode, that points to a storage in memory.
Now, when we give that imaginary filename as argument and when the command tries to open that imaginary file, it finds that it's inode entry is referring to a storage in memory. But the actual content is there in disk and not in the memory. So, the data is not loaded into memory yet, unless we read it explicitly. Hence, again we would need to read the data first.
Simply saying, as there is no continuity or references at storage in disk to the next file data, the merged data needs to be loaded to memory first.
So, with my deduction, it seems we would at least need to put the data in memory. However, as the command itself would need the file to be read (if not the whole file, at least a part of it until the commands's operation is done - let it be parsing or whatever). So, using this method, we could save some significant IO, if it's really a big file.
So, how can we create that virtual file?
My first answer is to write the merged file to tmpfs and refer to that file. But is it the only option or can we actually point to a storage location in memory, other than tmpfs? tmpfs is not option because, my script can be run from any server and we need to have a solution that work from all servers. If I mention to create merged file at /dev/shm in my script, it may fail in the server where it doesn't have /dev/shm. So I should be able to load to memory directly. But I think normal user will not have access to memory and so, it seems can not be done without shm.
Please let me know your comments and also kindly correct me if my understanding anywhere is wrong. Even if it is complicated for my level, kindly post your answer. At least, I might understand it after few months.
Create a fifo (named pipe) and provide its name as an argument to your program. The process that combines the five input files writes to this fifo
mkfifo wtf
cat file1 file2 file3 file4 file5 > wtf # this will block...
[from another terminal] cp wtf omg
Here I used cp as your program, and cat as the program combining the five files. You will see that omg will contain the output of your program (here: cp) and that the first terminal will unblock after the program is done.
Your program (here:cp) is not even aware that its 1st argument wtf refers to a fifo; it just opens it and reads from it like it would do with an ordinary file. (this will fail if the program attempts to seek in the file; seek() is not implemented for pipes and fifos)
Could someone help me in writing a program that has to compile all the files in the directory and report error, if any. For which my program has to get the list of all files under the folder with its full path and store it in a temp-table and then it has to loop through the temp table and compile the files.
Below is a very rough start.
Look for more info around the COMPILE statement and the COMPILER system handle in the online help (F1).
Be aware that compiling requires you to have a developer license installed. Without it the COMPILE statement will fail.
DEFINE VARIABLE cDir AS CHARACTER NO-UNDO.
DEFINE VARIABLE cFile AS CHARACTER NO-UNDO FORMAT "x(30)".
ASSIGN
cDir = "c:\temp\".
INPUT FROM OS-DIR(cDir).
REPEAT:
IMPORT cFile.
IF cFile MATCHES "*..p" THEN DO:
COMPILE VALUE(cDir + cFile) SAVE NO-ERROR.
IF COMPILER:ERROR THEN DO:
DISPLAY
cFile
COMPILER:GET-MESSAGE(1) FORMAT "x(60)"
WITH FRAME frame1 WIDTH 300 20 DOWN.
END.
END.
END.
INPUT CLOSE.
Since the comment wouldn't let me paste this much into it... using INPUT FROM OS-DIR returns all of the files and directories under a directory. You can use this information to keep going down the directory tree to find all sub directories
OS-DIR documentation:
Sometimes, rather than reading the contents of a file, you want to read a list of the files in a directory. You can use the OS–DIR option of the INPUT FROM statement for this purpose.
Each line read from OS–DIR contains three values:
*The simple (base) name of the file.
*The full pathname of the file.
*A string value containing one or more attribute characters. These characters indicate the type of the file and its status.
Every file has one of the following attribute characters:
*F — Regular file or FIFO pipe
*D — Directory
*S — Special device
*X — Unknown file type
In addition, the attribute string for each file might contain one or more of the following attribute characters:
*H — Hidden file
*L — Symbolic link
*P — Pipe file
The tokens are returned in the standard ABL format that can be read by the IMPORT or SET statements.
I am trying to delete all files from a directory apart from two (which will be erased, then re-written). One of these files, not to be deleted, contains the names of all files in the folder/directory (the other contains the number of files in the directory).
I believe there (possibly?) are 2 solutions:
Read the names of each file from the un-deleted file and delete them individually until there is only the final 2 files remaining,
or...
Because all other files end in .txt I could use some sort of filter which would only delete files with this ending.
Which of these 2 would be most efficient and how could it be done?
Any help would be appreciated.
You are going to end up deleting files one by one, regardless of which method you use. Any optimizations you make are going to be very miniscule. Without actually timing your algorithms, I'd say they'd both take about the same amount of time (and this would vary from one computer to the next, based on CPU speed, HDD type, etc). So, instead of debating that, I'll provide you code for both the ways you've mentioned:
Method1:
import os
def deleteAll(infilepath):
with open(infilepath) as infile:
for line in infile:
os.remove(line)
Method 2:
import os
def deleteAll():
blacklist = set(['names/of/files/to/be/deleted', 'number/of/files'])
for fname in (f for f in os.listdir() if f not in blacklist):
os.remove(fname)
Gentlemen and -women, one question regarding programming:
I need to write a program to batch rename files, under the following conditions:
All files are in a directory. Can be the same directory as the executable, if this simplifies things pathwise.
All files are .pdf, but this should not matter I believe.
All files are start with double digit prefix ranging from 01 til 99
e.g.: 01_FileNameOriginal1.pdf ; 02_FileNameOriginal2.pdf
This double digit needs to stay.
All names must be modified to a predefined range of filenames (sort of a database) which can be embedded in the executable, or read out from an external file (being .txt, .csv, whatever is most feasible).
e.g.: 01_NewName1.pdf ; 02_NewName2.pdf ; ...
Some original filenames contain an expiry date, which is labelled "EXP YYYY MMM DD", and should be appended to the new name in a different format: "e_YYYY_MM_DD". So basically it needs to be able to use a "for" statement (to loop for the number of files), and "if" statement to search for the "EXP" (matching case), cut the string and append in the rearranged format before the file extension.
The result of the program can be either renaming, or returning a renamed copy. The former seems easier to do.
So to summarize:
step 1: Run program
step 2: integer = number of files
step 3: loop:
3.A check first two digits, copy to a temp string.
3.B compare the temp string with an array of predefined filenames. The array can be embedded in the code, or read externally. For the user friendliness sake, an external read from a .csv seems easier to modify the filenames later on.
3.C to rename or not to rename. In case of a match:
Assume the old file has the following name: 01_FileNameOriginal EXP YYYY MM DD Excessivetext.pdf
Copy first two digits to a temp2 string
Scan the old name for EXP (for length of filename, if = "EXP ", matching case) and cut the following 10 characters. Put YYYY, MM, and DD in seperate strings. All essential value has now been extracted from the old filename.
Match dbl digits with the first two digits of filenames in the database. Copy the new name to a temp string.
Rename the file with the new name: eg 01_NewName.pdf
Append date strings before the extension: eg 01_NewName_e_YYYY_MM_DD.pdf
Note: date can be extracted in a single string, as long as spaces are replaced by underscores. Probably shorter to program.
in case of no match: copy old filename, and put it in a temp string, to return at the end of the process as an error (or .txt) file, with filenames that could not be renamed.
step 4: finish and return error or report (see previous)
Question:
Based on these conditions, what would be the easiest way to get started? Are there freeware programs that can do such a thing. If not what is the best approach. I have basic programming knowledge (Java/VBA), some small C++ stints but nothing spectacular. I have only programmed in a programming environment and have never produced any executables or batch files or the likes so I don't have any idea how to start atm, but wouldn't mind to give it a shot. As long as it's a guided shot, and not one in the dark cos that's where I am now.
Would love to hear some thoughts on this.
Greetings
Wouter