I'm starting with Lisp and I have quite a problem trying to read a matrix from a file to program the A-star algorithm.
The file I should read has at the top of the file the number of rows and the number of columns, and I must save both in a couple of global variables (N and M). The file format should be something like this:
ROWS
5
COLUMNS
5
MATRIX
1 1 1 1 1
1 1 0 1 1
1 1 0 0 1
1 1 0 0 1
1 1 1 0 1
START
4 2
GOAL
4 4
And my code at the moment is like this (It's probably quite lame, by the way)
(defvar *N*)
(defvar *M*)
(defvar *Goal*)
(defvar *Start*)
(defvar *Matrix*)
(defun read-matrix (file)
(let ((in (OPEN file :DIRECTION :INPUT)))
(read-line in nil)
(SETQ *N* (parse-integer (read-char in)))
(read-line in nil)
(read-line in nil)
(SETQ *M* (parse-integer (read-line in)))
(read-line in nil)
(read-line in nil)
(SETQ *Matrix* (MAKE-ARRAY '(*N* *M*) :initial-element 1))
(loop for i from 0 to *N*
do (loop for j from 0 to *M*
do (SETF (AREF *Matrix* i j)
(read-char in))))
(read-line in nil)
(SETQ *Start* (read-line in))
(read-line in nil)
(SETQ *Goal* (read-line in))))
I'd be really grateful if anybody could help me.
There are a bunch of errors.
you open a stream, but never close it
you call PARSE-INTEGER on a character, when it expects a string
MAKE-ARRAY is called with a list of symbols, but it should be a list of numbers.
Also: using global variables is not a good idea.
First problem: you open a file, but never close it. When you use open, you need to remember to close afterwards. Since this is so common, there is with-open-file, which closes automatically (see the CLHS for documentation).
(defvar *N*)
(defvar *M*)
(defvar *Goal*)
(defvar *Start*)
(defvar *Matrix*)
(defun read-matrix (file)
(with-open-file (in file
:direction :input)
(read-line in nil)
(setq *N* (parse-integer (read-char in)))
(read-line in nil)
(read-line in nil)
(setq *M* (parse-integer (read-line in)))
(read-line in nil)
(read-line in nil)
(setq *Matrix* (make-array '(*N* *M*) :initial-element 1))
(loop for i from 0 to *N*
do (loop for j from 0 to *M*
do (setf (aref *Matrix* i j)
(read-char in))))
(read-line in nil)
(setq *Start* (read-line in))
(read-line in nil)
(setq *Goal* (read-line in))))
Second problem: read-char returns a character, and parse-integer is not defined for a character. It seems that you can simply read the entire line.
(defvar *N*)
(defvar *M*)
(defvar *Goal*)
(defvar *Start*)
(defvar *Matrix*)
(defun read-matrix (file)
(with-open-file (in file
:direction :input)
(read-line in nil)
(setq *N* (parse-integer (read-line in)))
(read-line in nil)
(read-line in nil)
(setq *M* (parse-integer (read-line in)))
(read-line in nil)
(read-line in nil)
(setq *Matrix* (make-array '(*N* *M*) :initial-element 1))
(loop for i from 0 to *N*
do (loop for j from 0 to *M*
do (setf (aref *Matrix* i j)
(read-char in))))
(read-line in nil)
(setq *Start* (read-line in))
(read-line in nil)
(setq *Goal* (read-line in))))
Third problem: it seems that you discard too many lines between those that you need.
(defvar *N*)
(defvar *M*)
(defvar *Goal*)
(defvar *Start*)
(defvar *Matrix*)
(defun read-matrix (file)
(with-open-file (in file
:direction :input)
(read-line in nil) ; "ROWS"
(setq *N* (parse-integer (read-line in)))
(read-line in nil) ; "COLUMNS"
(setq *M* (parse-integer (read-line in)))
(read-line in nil) ; "MATRIX"
(setq *Matrix* (make-array '(*N* *M*) :initial-element 1))
(loop for i from 0 to *N*
do (loop for j from 0 to *M*
do (setf (aref *Matrix* i j)
(read-char in))))
(read-line in nil) ; "START"
(setq *Start* (read-line in))
(read-line in nil) ; "GOAL"
(setq *Goal* (read-line in))))
Fourth problem: that make-array form needs numbers to set its dimensions, but you give it two symbols instead. You need to evaluate those symbols to get the values you want:
(defvar *N*)
(defvar *M*)
(defvar *Goal*)
(defvar *Start*)
(defvar *Matrix*)
(defun read-matrix (file)
(with-open-file (in file
:direction :input)
(read-line in nil) ; "ROWS"
(setq *N* (parse-integer (read-line in)))
(read-line in nil) ; "COLUMNS"
(setq *M* (parse-integer (read-line in)))
(read-line in nil) ; "MATRIX"
(setq *Matrix* (make-array (list *N* *M*) :initial-element 1))
(loop for i from 0 to *N*
do (loop for j from 0 to *M*
do (setf (aref *Matrix* i j)
(read-char in))))
(read-line in nil) ; "START"
(setq *Start* (read-line in))
(read-line in nil) ; "GOAL"
(setq *Goal* (read-line in))))
Fifth problem: It seems that you want your matrix to contain bits, but you only put characters into it. On top of that, your matrix will not even contain a #\1 (the character) where you expect a 1 (the number) and a #\0 where you expect a 0, but instead the following (assuming Unix-style newlines):
#2A((#\1 #\Space #\1 #\Space #\1)
(#\Space #\1 #\Space #\1 #\Newline)
(#\1 #\Space #\1 #\Space #\0)
(#\Space #\1 #\Space #\1 #\Newline)
(#\1 #\Space #\1 #\Space #\0))
To accomplish what you want, I propose to read the matrix lines, then split them on spaces and parse the fields. A handy library for that is split-sequence.
(defvar *N*)
(defvar *M*)
(defvar *Goal*)
(defvar *Start*)
(defvar *Matrix*)
(defun read-matrix (file)
(with-open-file (in file
:direction :input)
(read-line in nil) ; "ROWS"
(setq *N* (parse-integer (read-line in)))
(read-line in nil) ; "COLUMNS"
(setq *M* (parse-integer (read-line in)))
(read-line in nil) ; "MATRIX"
(setq *Matrix* (make-array (list *N* *M*) :initial-element 1))
(loop :for i :from 0 :to *N*
:do (let* ((line (read-line in))
(fields (split-sequence:split-sequence #\Space line))))
(loop :for field :in fields
:for j :upfrom 0
:do (setf (aref *matrix* i j)
(parse-integer field))))
(read-line in nil) ; "START"
(setq *Start* (read-line in))
(read-line in nil) ; "GOAL"
(setq *Goal* (read-line in))))
At this point, this should "work", for some value of "work" (I have not tested it, though).
However, the use of global variables for transfer of information has been established as uncouth about 30 years ago. The first step of bringing this to a style more suitable for a robust program would be to return the values from the function instead.
(defun read-matrix (file)
(let (n m goal start matrix)
(with-open-file (in file
:direction :input)
(read-line in nil) ; "ROWS"
(setf n (parse-integer (read-line in)))
(read-line in nil) ; "COLUMNS"
(setf m (parse-integer (read-line in)))
(read-line in nil) ; "MATRIX"
(setf matrix (make-array (list n m) :initial-element 1))
(loop :for i :from 0 :to n
:do (let* ((line (read-line in))
(fields (split-sequence:split-sequence #\Space line))))
(loop :for field :in fields
:for j :upfrom 0
:do (setf (aref matrix i j)
(parse-integer field))))
(read-line in nil) ; "START"
(setf start (read-line in))
(read-line in nil) ; "GOAL"
(setf goal (read-line in)))
(values n m goal start matrix)))
I think that what you really need is a struct or class for what you are reading in here. It might be called game-state.
(defclass game-state ()
((rows :accessor rows :initarg :rows)
(columns :accessor columns :initarg :columns)
(goal :accessor goal :initarg :goal)
(start :accessor start :initarg :start)
(board :accessor board :initarg board)))
Your function should then be named read-game-state and return an object of this class.
Something to get you started:
(defvar *rows*)
(defvar *columns*)
(defvar *goal*)
(defvar *start*)
(defvar *matrix*)
(defun parse-sequence (tokens parser &key (delimiter #\Space))
(do ((start 0)
(end (position delimiter tokens :start 0)
(position delimiter tokens :start start))
result)
;; you could also read from end to avoid reversing
;; I just find this to be more natural
((null end)
(reverse
(cons
(funcall parser
(subseq tokens start (length tokens)))
result)))
(setf result
(cons (funcall parser
(subseq tokens start end)) result))
(setf start (1+ end))))
(defun parse-matrix-source (file)
(with-open-file (stream file :direction :input)
(do ((line (read-line stream nil :eof)
(read-line stream nil :eof))
(matrix-row 0)
(matrix-column 0 0)
state)
((eq line :eof))
(cond
((string= line "ROWS") (setf state '*rows*))
((string= line "COLUMNS") (setf state '*columns*))
((string= line "MATRIX") (setf state '*matrix*))
((string= line "START") (setf state '*start*))
((string= line "GOAL") (setf state '*goal*))
((eq state '*rows*) (setf *rows* (parse-integer line)))
((eq state '*columns*)
(setf *columns* (parse-integer line)
*matrix* (make-array (list *rows* *columns*)
:initial-element 0)))
((eq state '*matrix*)
(dolist (i (parse-sequence line 'parse-integer))
(setf (aref *matrix* matrix-column matrix-row) i
matrix-column (1+ matrix-column)))
(incf matrix-row))
((eq state '*start*)
(setf *start* (parse-sequence line 'parse-integer)))
((eq state '*goal*)
(setf *goal* (parse-sequence line 'parse-integer)))))))
(defun test-parse-matrix ()
(parse-matrix-source #P"~/Projects/lisp-doodles/matrix.txt")
(format t "rows: ~d, columns: ~d, goal: ~s, start: ~s~& matrix: ~s~&"
*rows* *columns* *goal* *start* *matrix*))
(test-parse-matrix)
But I would think about a better format - in which case you have a good chance of finding a library that is already able to parse it + will allow for more flexibility in the future. Even CSV or INI would be better then what you have now.
Related
I am using Common Lisp (SBCL). Currently, I can write a file appending lists. In order to illustrate, let's first define some variables to make it easier to understand:
(defvar example-1 '((d-1 u-1) (i-1) (i-2)))
(defvar example-2 '((d-2 u-2) (i-1) (i-2)))
Now, in the REPL:
CL-USER> (with-open-file (str "/home/pedro/miscellaneous/misc/tests-output/stack-overflow.lisp"
:direction :output
:if-exists :append
:if-does-not-exist :create)
(format str " ~S ~%" example-1))
CL-USER> (with-open-file (str "/home/pedro/miscellaneous/misc/tests-output/stack-overflow.lisp"
:direction :output
:if-exists :append
:if-does-not-exist :create)
(format str " ~S ~%" example-2))
If I go to checkout the file "stack-overflow.lisp", I can see:
((D-1 U-1) (I-1) (I-2))
((D-2 U-2) (I-1) (I-2))
Ok. This is close to what I want.
However, I would like to have everything wrapped in a list:
(
((D-1 U-1) (I-1) (I-2))
((D-2 U-2) (I-1) (I-2))
)
Thus, every time something is "appended" to the file, it should be inside this list. I am changing this because it will make this file easier to read and work on. I will need to filter the elements added.
What do I need to change in with-open-file function to have this output?
Use a single call to WITH-OPEN-FILE and combine everything into a single list.
(with-open-file (str "/home/pedro/miscellaneous/misc/tests-output/stack-overflow.lisp"
:direction :output
:if-exists :overwrite
:if-does-not-exist :create)
(pprint (list example-1 example-2) str))
If you want to append to the list every time you write, you need to read the list, append to the list, then overwrite the file with the updated list.
(defun append-to-list-in-file (filename new-item &aux contents) ;;;;
(setq contents (list)) ;; default in case reading fails
(ignore-errors
(with-open-file (str filename :direction :input)
(setq contents (read str))))
(setq contents (nconc contents (list new-item)))
(with-open-file (str filename :direction :output :if-exists :overwrite)
(write contents :stream str)))
(define (domain dungeon_hero)
(:requirements :strips :adl :typing :negative-preconditions) ;; There is use of negative-preconditions in the domain
(:types hero room sword monster trap clear) ;; Various types used
(:predicates (connected ?r1 -room ?r2 -room) ;; defines that two rooms are connected and will be defined twice for bi-directional connection
(at-hero ?h -hero ?r -room) ;; defines what is there in which room i.e.hero,trap,monster or room is clear
(monster-in ?m -monster ?r1 -room) ;; defines a monster in a room
(trap-in ?t -trap ?r -room) ;; defines a trap in a room
(sword-in ?s -sword ?r -room) ;; defines a sword in a room
(clear-room ?r -room) ;; defined a room is clear
(handempty ?h -hero ) ;; TRUE if the Hero's hand is empty
(haveSword ?s -sword ) ;; if the hero is holding the sword
(alive ?h -hero) ;; If the hero is alive
(exist ?r -room) ;; If the room is not destroyed i.e. after the visit
)
(:action visit2sword
:parameters (?h -hero ?from -room ?to -room ?s -sword ?t -trap ?m -monster)
:precondition (and (at-hero ?h ?from)(alive ?h) (exist ?to)(sword-in ?s ?to)(connected ?from ?to) )
:effect
(and (not (at-hero ?h ?from))
(not(exist ?from))
(at-hero ?h ?to)
(not(monster-in ?m ?to))
(not(trap-in ?t ?to))
)
)
(:action pick-up-sword
:parameters (?h -hero ?r -room ?s -sword )
:precondition (and (at-hero ?h ?r)(alive ?h) (sword-in ?s ?r) (handempty ?h))
:effect
(and (not (handempty ?h))
(haveSword ?s)
(not(sword-in ?s ?r))
)
)
(:action visit2monster
:parameters (?h -hero ?from -room ?to -room ?s -sword ?m -monster ?t -trap)
:precondition (and (at-hero ?h ?from)(alive ?h)(not(handempty ?h))(exist ?to)(haveSword ?s)(monster-in ?m ?to) (connected ?from ?to) )
:effect
(and (not (at-hero ?h ?from))
(not(exist ?from))
(at-hero ?h ?to)
(not(trap-in ?t ?to))
(not(sword-in ?s ?to))
)
)
(:action visit2trap
:parameters (?h -hero ?from -room ?to -room ?s -sword ?t -trap)
:precondition (and (at-hero ?h ?from)(alive ?h)(not(haveSword ?s))(handempty ?h)(trap-in ?t ?to) (exist ?to)(connected ?from ?to) )
:effect
(and (not (at-hero ?h ?from))
(not(exist ?from))
(at-hero ?h ?to)
)
)
(:action visit2clear
:parameters (?h -hero ?from -room ?to -room ?s -sword ?t -trap ?m -monster)
:precondition (and (at-hero ?h ?from)(alive ?h)(clear-room ?to)(exist ?to)(connected ?from ?to) )
:effect
(and (not (at-hero ?h ?from))
(not(exist ?from))
(at-hero ?h ?to)
(not(monster-in ?m ?to))
(not(trap-in ?t ?to))
;; (not(sword-in ?s ?to))
)
)
(:action destroy-sword
:parameters (?h -hero ?t -trap ?s -sword ?m -monster ?r -room )
:precondition
((at-hero ?h ?r)(alive ?h) (haveSword ?s)(not(monster-in ?m ?r)) (not(trap-in ?t ?r)
;;(and(at-hero ?h ?r)(alive ?h) (haveSword ?s)(not(monster-in ?m ?r)) (not(trap-in ?t ?r))
)
:effect
(and (handempty ?h)
(not(haveSword ?s))
)
)
(:action disarm ;; Disarm the trap to
:parameters (?h -hero ?t -trap ?r -room )
:precondition (and(at-hero ?h ?r)(alive ?h)(handempty ?h)(trap-in ?t ?r) )
:effect
(and (not (trap-in ?t ?r))
)
)
(:action getkilled ;; Disarm the trap to
:parameters (?h -hero ?m -monster ?s -sword ?r -room )
:precondition (and(at-hero ?h ?r)(alive ?h)(handempty ?h)(monster-in ?m ?r)(not(haveSword ?s))) ;;
:effect
(and (not (alive ?h))
)
)
)
(define (problem T2PROBLEM-1)
(:domain dungeon_hero)
(:objects d0-1 d1-1 d1-2 d2-1 d2-2 d3-1 d3-2 d4-1 - room
sword1 - sword
mon1 mon2 - monster
clear1 clear2 - clear
trap1 - trap
h1 - hero
;;h1 -hand
)
(:INIT
;; Initial status of the hero h1
(at-hero h1 d0-1)
(alive h1)
(handempty h1)
;; content of each room
(sword-in sword1 d1-1)
(monster-in mon1 d1-2)
(monster-in mon2 d2-1)
(trap-in trap1 d1-1)
(clear-room d2-2)
(clear-room d3-2)
(clear-room d4-1)
;; All rooms exist at the initial state and hence exist should be TRUE
(exist d0-1) (exist d1-1) (exist d1-2) (exist d2-1) (exist d2-2)
(exist d3-1) (exist d3-2) (exist d4-1)
;; Rooms are connected in both direction being undirected graph
(connected d0-1 d1-1)(connected d1-1 d0-1)
(connected d0-1 d1-2)(connected d1-2 d0-1)
(connected d1-1 d1-2)(connected d1-2 d1-1)
(connected d1-1 d2-1)(connected d2-1 d1-1)
(connected d1-2 d2-2)(connected d2-2 d1-2)
(connected d2-1 d2-2)(connected d2-2 d2-1)
(connected d2-1 d3-1)(connected d3-1 d2-1)
(connected d2-2 d3-2)(connected d3-2 d2-2)
(connected d3-1 d3-2)(connected d3-2 d3-1)
(connected d3-1 d4-1)(connected d4-1 d3-1)
)
(:goal (AND (ALIVE h1) (AT-hero h1 d4-1)))
)
Your domain model is not valid PDDL.
Action "destroy-sword" has an "incorrect" precondition specification.
:precondition
((at-hero ?h ?r)(alive ?h) (haveSword ?s)(not(monster-in ?m ?r)) (not(trap-in ?t ?r)
;;(and(at-hero ?h ?r)(alive ?h) (haveSword ?s)(not(monster-in ?m ?r)) (not(trap-in ?t ?r))
)
Should this precondition be as follows?
:precondition
(and (at-hero ?h ?r)(alive ?h) (haveSword ?s)(not(monster-in ?m ?r)) (not(trap-in ?t ?r)
;;(and(at-hero ?h ?r)(alive ?h) (haveSword ?s)(not(monster-in ?m ?r)) (not(trap-in ?t ?r))
))
If you specify it as described above, the output of the planner is " ff: goal can be simplified to FALSE. No plan will solve it". So the specified planning problem has no plan.
I am trying to use CVC4 to perform syntax-guided synthesis on a function. To begin, I am following CVC4 Getting Started and my example.smt2 file looks like this:
(set-logic ALL)
(declare-fun x () Int)
(assert (= (+ x x) (* x 2)))
(check-sat)
When I run cvc4 example.smt2 in command line in the directory, there should not be a problem based on the tutorial linked above. However, I get this error:
(error "Couldn't open file: example.smt2")
Note that this error is not the same as if the file does not exist. For example, when I run cvc4 exampl.doesnotexist, the following error occurs:
CVC4 Error:
Couldn't open file: exampl.doesnotexist
Which is different. I looked this up everywhere but could not find an answer. Any ideas?
You'll get this error if you don't have read-permissions on the file:
$ cat example.smt2
(set-logic ALL)
(declare-fun x () Int)
(assert (= (+ x x) (* x 2)))
(check-sat)
$ cvc4 example.smt2
sat
$ chmod u-r example.smt2
$ cvc4 example.smt2
(error "Couldn't open file: example.smt2")
$ cat example.smt2
cat: example.smt2: Permission denied
$ chmod u+r example.smt2
$ cat example.smt2
(set-logic ALL)
(declare-fun x () Int)
(assert (= (+ x x) (* x 2)))
(check-sat)
$ cvc4 example.smt2
sat
Depending on your operating system, simply allow read access and the problem should go away.
I have a data structure:
*game-board*
#(#() #() #() #() #() #() #())
CL-USER> (test-move)
1
CL-USER> *game-board*
#(("O" "X" . #()) ("O" "X" . #()) ("X" "O" "O" "X" . #())
("X" "O" "X" "O" . #()) ("O" "X" . #()) ("X" . #()) ("X" "O" . #()))
CL-USER> (print-game-board)
("O" "X" . #())
("O" "X" . #())
("X" "O" "O" "X" . #())
("X" "O" "X" "O" . #())
("O" "X" . #())
("X" . #())
("X" "O" . #())
NIL
CL-USER>
My question is how do I get the dimensions of the inner vectors?
(array-dimension game-board 0) returns 7 for the length of the outer vector. How ever I can't figure out how to get the dimensions of the inner vectors.
my code that fills the board is this: (run once for every input)
(defun move (c)
(let ((place (gethash c *col-lookup*)))
(cond ((oddp *turn-count*)
(push "X" (aref *game-board* place))
(incf *turn-count*))
((push "O" (aref *game-board* place))
(incf *turn-count*)))))
I used vectors because I figured I could just push moves onto the board and when checking if the length was less then 4 then just skip the horizontal check for that row.
Am I thinking about optimizing even before I have a solution? That's probably unwise, I probably should just hack something together using a 2d array as suggested.
That is because you you have no inner vectors. Although you start with with a vector of with with 7 vectors, after (test-move) you have a vector with 7 improper lists whose last element is a #(). Can you add the code to test-move so we can help you see what went wrong there?
Btw, I'm guessing you are using a list of lists representation when you want to represent a grid? If so may I suggest using a multidimensional array or just a vector and some helper functions to move from a pair to an index and back.
The inner arrays might not be all have the same dimension, so here's an example that gives you the largest dimension:
(defun inner-dimension (outer-array)
(loop for inner-array being the elements of outer-array
maximize (array-dimension inner-array 0)))
If you want the inner arrays to be all the same size, you should use a single multi-dimensional array instead of an array of arrays.
I know how to delimit a string sequence by a space:
(defun ff-cols (dir file)
(with-open-file (ff-cols-str pathname :direction :input)
(length (split-sequence #\Space (read-line ff-cols-str nil 'eof)))))
But how do you delimit a sequence by a double space? Often flat files have
columns separated by double spaces.
(split-sequence " " "1 2 3 4")
returns
("1 2 3 4") ;
10
Also,
(split-sequence #\Space "1 2 3 4")
returns
("1" "" "2" "" "3" "" "4") ;
10
Try this instead:
(split-sequence-if (lambda (s) (equal s " ")) "1 2 3 4")
Or this:
(split-sequence #\Space "1 2 3 4" :remove-empty-subseqs t)
(ql:quickload "cl-ppcre")
(cl-ppcre:split "\\s\\s" "One Two Three Four Five")
("One" "Two Three" "Four" " Five")
Obviously, whatever you could've learned from other languages which also make use of regular expressions applies.
This could be because your string is not seperated by double spaces
(split-sequence " " "1 2 3 4")
try
(split-sequence " " "1 2 3 4")