In this post I'll discuss the steps to finalise the Mars Rover Tech Challenge.
In particular how to read the configuration settings from a file and then run the code.
As with the last post, please forgive all the spurious
(princ ...), it's the
only way I know to get Org mode to export the results for inclusion in this blog
This series of posts
This is the second of three posts on this topic. I'll update these links as I publish the other posts. I've used lisp (the second oldest programming language still in use!) for this, and in the third post I'll explore why:
- Part 1 : Initial code - running from a REPL
- Part 2 : Read the configuration file and running from the command line
- Part 3 : Benchmarking (AKA why this funny looking language?)
Get the code
You can download the code from for this and the previous post from my GitHub account:
Load the previous code
Read in the input file
Here we use the UIOP
read-file-lines function for cross lisp-implementation
and cross machine support.
(defun read-input-file (filename) ;; Throw away blank lines (let ((setup (loop for line in (uiop:read-file-lines filename) if (not (equal line "")) collect (read-from-string (concatenate 'string "(" line ")"))))) (set-plateau-size (car setup)) (loop-over-starting-positions (cdr setup))))
I've also used a
- convert the input into a list whilst
- throwing away blank lines; and
- converting the text of each line into a list
I've made an assumption that the input file is of the form:
X_MAX Y_MAX START_X_1 START_Y_1 ORIENTATION_1 COMMANDS_1 START_X_2 START_Y_2 ORIENTATION_2 COMMANDS_2 ...
…but what does this variable
setup actually look like?
(princ (loop for line in (uiop:read-file-lines "mars-rover-test-input.txt") if (not (equal line "")) collect (read-from-string (concatenate 'string "(" line ")"))))
((5 5) (1 2 N) (LMLMLMLMM) (3 3 E) (MMRMMRMRRM))
Set the plateau size
This is only really needed as I'm not writing this in the functional style.
The plateau shape is the first element in the
(defun set-plateau-size (plateau-shape) (setf *x_max* (car plateau-shape)) (setf *y_max* (cadr plateau-shape)))
Loop over each of the starting positions
This is probably the least transparent piece of code in this discussion.
Let's work through the logic: What do we need for running
Firstly we need the
orientation. Looking at the results above, the
(1 2 N) has all the information. So the
pos is simply a
cons of the
car) and second (
cadr) elements. The
orientation is the third
Now what about the actual
command? They need to be a list of the individual
letters in the third (
cadr) list. So we need the
car of that list.
Initially I thought something like this would work:
(let ((cmds_list '(LMLMLMLMM))) (coerce (car cmds_list) 'list))
but it results in the error:
The value LMLMLMLMM is not of type SEQUENCE.
Let's think about that for a moment. Here
LMLMLMLMM isn't a string of letters,
it's simply an atom (think an object/variable in other languages). Lisp doesn't
see any difference between it and any other atom. To get it treated like a
string of letters we need to convert it to a string (and that's the
give-away in the error message). So let's try:
(let ((cmds_list '(LMLMLMLMM))) (princ (coerce (string (car cmds_list)) 'list)))
(L M L M L M L M M)
Perfect! So let's go…
The code to loop over the elements of the configuration file
Here we take the first two elements of the
input list, convert them to
update-position and then call this function
again with the remainder of the input list. Recursion again!
(defun loop-over-starting-positions (input) "Loop over the starting poition/orientation and commands INPUT is a list of pairs of lists (x y orientation) (commands)" (when input (let* ((start (car input)) (commands (coerce (string (car (cadr input))) 'list)) (pos (cons (car start) (cadr start))) (orientation (caddr start))) (update-position pos orientation commands)) (loop-over-starting-positions (cddr input))))
Note - I've left this code reading
(car (cadr input)) rather than simplifying
(caadr input) to make it more consistent with the explanation above. (If
that's all Greek to you, do some more learning about lisp!)
Run the code over the input file
1 3 N 5 1 E
This is the expected output. Success!
Now let's make a command line tool
This is a remarkably easy process using Roswell.
I'll make the assumption that you've installed Roswell and have it working.
Scripting with Roswell
The script to run the code we've just developed is the following:
#!/bin/sh #|-*- mode:lisp -*-|# #| exec ros -Q -- $0 "$@" |# (progn ;;init forms (ros:ensure-asdf) #+quicklisp(ql:quickload '() :silent t) ) (defpackage :ros.script.rover.3790038153 (:use :cl)) (in-package :ros.script.rover.3790038153) (load "mars-rover-v2.lisp") (defun main (filename &rest argv) (declare (ignorable argv)) (read-input-file filename)) ;;; vim: set ft=lisp lisp:
There are only two lines of interest, and what they do is pretty obvious.
(load "mars-rover-v2.lisp") ;; ...and (read-input-file filename)
Now we can simply run the code like:
In the next post I'll talk about benchmarking this implementation - and why use Lisp.