I have some lisp number strings string to parse as numbers:
"3.000000000E+00"
"0.0d0"
"3"
Please use the parse-number library, available from Quicklisp.
(define-condition argument-not-numeric-string (error)
((text :initarg :text :reader text)))
(defun string-to-number (str &optional (radix 10))
(let* ((*read-base* radix)
(value (and (stringp str)
(ignore-errors
(with-input-from-string (in str)
(read in))))))
(if (numberp value)
value
(flet ((read-new-value () (format t "Enter new value: ") (multiple-value-list (eval (read)))))
(restart-case (error 'argument-not-numeric-string :text "Argument needs to be a string with an numeric value")
(use-value (value) :interactive read-new-value (progn (assert (numberp value) (value) "value isn't numeric") value))
(restart-with-string (string) :interactive read-new-value (string-to-number string radix)))))))
Here are the tests:
(string-to-number "3.000000000E+00") ; ==> 3.0
(string-to-number "0.0d0") ; ==> 0.0d0
(string-to-number "3") ; ==> 3
(handler-bind ((argument-not-numeric-string #'(lambda (c) (invoke-restart 'restart-with-string "0"))))
(string-to-number "hello")) ; ==> 0
(handler-bind ((argument-not-numeric-string #'(lambda (c) (use-value 0))))
(string-to-number "hello")) ; ==> 0
(string-to-number "aa" 16) ; ==> 170
(string-to-number "gf" 16)
*** - Condition of type ARGUMENT-NOT-NUMERIC-STRING.
The following restarts are available:
USE-VALUE :R1 USE-VALUE
RESTART-WITH-STRING :R2 RESTART-WITH-STRING
ABORT :R3 Abort main loop
Break 1 [50]> :r2
Enter new value: "ff"
; ==> 255
After google, refer to post:
* (with-input-from-string (in "3.000000000E+00")
(read in))
3.0
*
Related
What I'm trying to do: I want to define a function create-record that accepts a variable number of parameters (names called keys) and generates a dynamic plist/named list.
I'm just trying to get it working with dynamic names first and assign 1 to all the names to create a list like (:x 0 :y 0 :z 0) then I can modify it to accept both keys and values (instead of just 0 for everything key)
My code:
(defparameter *test* '('x 'y 'z))
(defun create-record (&rest keys)
(let ((count 0))
(mapcan (lambda (key) (list key count)) keys)))
Output
(create-record-variable *test*)
==> (('X 'Y 'Z) 0)
Expected Output:
(create-record-variable *test*)
==> (:x 0 :y 0 :z 0)
I'm not sure why the output is like (('X 'Y 'Z) 0).
The problem is not with the function, but with the invocation.
What you need to do is
(create-record 'a :b '#:c)
==> (A 0 :B 0 #:C 0)
or, if the keywords are in a list,
(defparameter *test* '(:x :y :z))
(apply #'create-record *test*)
==> (:X 0 :Y 0 :Z 0)
If you want to pass the list as the argument, you can just drop &rest.
Answering the question in the comment, here is how to create an alist
(association list):
(defun make-alist (keys values)
(mapcar #'cons keys values))
(defparameter *my-alist* (make-alist '(:a :b :c) '(1 2 3)))
(assoc :a *my-alist*)
==> (:A . 1)
and a plist (property list):
(defun make-plist (keys values)
(mapcan #'list keys values))
(defparameter *my-plist* (make-plist '(:a :b :c) '(1 2 3)))
(getf *my-plist* :b)
==> 2
(defun make-keyword (x)
"Make out of symbol or string a keyword."
(values (intern (string x) "KEYWORD")))
;; this is equivalent to alexandria's `alexandria::make-keyword
;; (defun make-keyword (x)
;; (read-from-string (concatenate 'string ":" (string x))))
;; ;; this doesn't work with "such strings"
(defun create-record (list-of-symbols &key (count 0))
(mapcan #'(lambda (x) (list (make-keyword x) count)) list-of-symbols))
Then call it:
(defparameter *test* (list 'x 'y 'z))
(create-record *test*)
;; => (:X 0 :Y 0 :Z 0)
(create-record *test* :count 3)
;; => (:X 3 :Y 3 :Z 3)
So I have a loop to just repeat the little text game I have made about dota, but when the function 'play' is called within a loop it doesn't return the result of my cond function, it just takes an input and then moves on to the next loop.
;;;;learn the invoker combo's
(defparameter *invoker-combo* '((cold-snap (3 0 0) 'QQQ);all of the possible invoker combo's
(ghost-walk (2 1 0) 'QQW)
(Ice-Wall (2 0 1) 'QQE)
(EMP (0 3 0) 'WWW)
(Tornado (1 2 0) 'QWW)
(Alacrity (0 2 1) 'WWE)
(Sun-Strike (0 0 3) 'EEE)
(Forge-Spirit (1 0 2) 'QEE)
(Chaos-Meteor (0 1 2) 'WEE)
(Deafening-Blast (1 1 1) 'QWE)))
(defun rand-combo (invoker-combo);returns a random combo
(nth (random (length invoker-combo))invoker-combo))
(defun count-letters (input);converts the keyboard strokes into numbers to be compared as it doesn't matter what order they are in, just that there is the correct quantity of them e.g QQE could also be written QEQ.
(append
(list (count #\Q input)
(count #\W input)
(count #\E input))))
(defun try-for-combo (rand-combo);takes i-p and compares it with the value for the random combo
(print(car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))'Good-job)
((equal i-p "END")(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo)*invoker-combo*)))))))
(defun play ()
(try-for-combo (rand-combo *invoker-combo*)))
(defun loop-play (x)
(loop for i from 0 to x
:do (play)))
If I call the function 'play' I get the following o/p:
FORGE-SPIRIT asdf
("ASDF" WAS WRONG CORRECT IS 'QEE)
or
ALACRITY wwe
GOOD-JOB
But if I call the function 'loop-play' I get the following o/p:
Break 3 [7]> (loop-play 2)
SUN-STRIKE eee
ALACRITY wwe
TORNADO qww
NIL
Can someone explain to me why this is happening?
EDIT: feel free to change the title, I didn't really know what to put.
The indentation and formatting of the code is poor. Please make it easier for you and for us to read the code.
(defun try-for-combo (rand-combo);takes i-p and compares it with the value for the random combo
(print(car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))'Good-job) ; wrong indent level
((equal i-p "END")(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo)*invoker-combo*)))))))
lacks spaces between s-expressions
wrong indentation levels
structure of the code unclear
does not use built-in documentation features
some lines are too long
Better:
(defun try-for-combo (rand-combo)
"takes i-p and compares it with the value for the random combo" ; built in doc
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo)) ; indentation
'Good-job)
((equal i-p "END")
(list 'Thanks 'for 'playing))
(t
(list i-p 'was 'wrong 'correct 'is ; several lines
(caddr (assoc (car rand-combo)
*invoker-combo*)))))))
I would propose to use an editor which actually understands some Lisp formatting. like GNU Emacs / SLIME, Clozure CL's Hemlock, LispWorks' editor...
If you are unsure about formatting, you can also ask Lisp to do it. Clisp is not that good at formatting, but something like SBCL or CCL would do:
* (let ((*print-case* :downcase))
(pprint '(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))
'Good-job) ((equal i-p "END")
(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is
(caddr (assoc (car rand-combo)
*invoker-combo*)))))))))
And you get nicely formatted code:
(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line))) (try (count-letters i-p)))
(cond ((equal try (cadr rand-combo)) 'good-job)
((equal i-p "END") (list 'thanks 'for 'playing))
(t
(list i-p 'was 'wrong 'correct 'is
(caddr (assoc (car rand-combo) *invoker-combo*)))))))
Automatic indenting of Lisp code by the editor saves you a lot of work.
There are hints for manual indentation.
Your try-for-combo function doesn't actually output anything. Rather, it returns values.
In the REPL, if you evaluate a form, like (+ 1 2), it will always print the evaluation of that form at the end (in this case, 3). However, consider instead (+ 1 (print 2)). The print function actually outputs the argument to standard output, then returns the value itself. So this will show (on the repl)
2
3
The 2 is outputted first, because (print 2) itself prints 2. Then, the form (+ 1 (print 2)) is evaluates to the same things as (+ 1 2), or 3.
In your case, your try-for-combo function should look like:
(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(print
(cond
((equal try (cadr rand-combo)) 'Good-job)
((equal i-p "END") (list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo) *invoker-combo*))))))
nil))
This will print the result of that cond form, and return 'nil'.
That's just the difference between the output your program does and the output the Lisp system does for each evaluation:
print prints something (a newline and then its argument) and returns a value. The value is printed by the REPL. Thus we see output twice:
[3]> (print "3")
"3"
"3"
Next we do several call to print in a progn. The value of the progn form is printed by the REPL. The first three strings are printed by the code and the last string is printed because of the Lisp REPL printing the value:
[4]> (progn (print "1") (print "2") (print "3"))
"1"
"2"
"3"
"3"
I'm new to lisp, and have been trying to learn Common Lisp by diving in and writing some code. I've read plenty of documentation on the subject, but it's taking a while to really sink in.
I have written a couple of macros (? and ??) for performing unit tests, but I'm having some difficulty. The code is at the end of the post, to avoid cluttering the actual question.
Here is an example of usage:
(??
(? "Arithmetic tests"
(? "Addition"
(= (+ 1 2) 3)
(= (+ 1 2 3) 6)
(= (+ -1 -3) -4))))
And an example of output:
[Arithmetic tests]
[Addition]
(PASS) '(= (+ 1 2) 3)'
(PASS) '(= (+ 1 2 3) 6)'
(PASS) '(= (+ -1 -3) -4)'
Results: 3 tests passed, 0 tests failed
Now, the existing code works. Unfortunately, the (? ...) macro is ugly, verbose, resistant to change - and I'm pretty sure also badly structured. For example, do I really have to use a list to store pieces of output code and then emit the contents at the end?
I'd like to modify the macro to permit description strings (or symbols) to optionally follow each test, whereupon it would replace the test literal in the output, thus:
(??
(? "Arithmetic tests"
(? "Addition"
(= (+ 1 2) 3) "Adding 1 and 2 results in 3"
(= (+ 1 2 3) 6)
(= (+ -1 -3) -4))))
Output:
[Arithmetic tests]
[Addition]
(PASS) Adding 1 and 2 results in 3
(PASS) '(= (+ 1 2 3) 6)'
(PASS) '(= (+ -1 -3) -4)'
But unfortunately I can't find a sensible place in the macro to insert this change. Depending on where I put it, I get errors like you're not inside a backquote expression, label is not defined or body-forms is not defined. I know what these errors mean, but I can't find a way to avoid them.
Also, I'll be wanting to handle exceptions in the test, and treat that as a failure. Currently, there is no exception handling code - the test result is merely tested against nil. Again, it is not clear how I should add this functionality.
I'm thinking that maybe this macro is over-complex, due to my inexperience in writing macros; and perhaps if I simplify it, modification will be easier. I don't really want to separate it out into several smaller macros without good reason; but maybe there's a terser way to write it?
Can anyone help me out here, please?
A complete code listing follows:
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,#body))
(defmacro while (condition &body body)
`(loop while ,condition do (progn ,#body)))
(defun flatten (L)
"Converts a list to single level."
(if (null L)
nil
(if (atom (first L))
(cons (first L) (flatten (rest L)))
(append (flatten (first L)) (flatten (rest L))))))
(defun starts-with-p (str1 str2)
"Determine whether `str1` starts with `str2`"
(let ((p (search str2 str1)))
(and p (= 0 p))))
(defmacro pop-first-char (string)
`(with-gensyms (c)
(if (> (length ,string) 0)
(progn
(setf c (schar ,string 0))
(if (> (length ,string) 1)
(setf ,string (subseq ,string 1))
(setf ,string ""))))
c))
(defmacro pop-chars (string count)
`(with-gensyms (result)
(setf result ())
(dotimes (index ,count)
(push (pop-first-char ,string) result))
result))
(defun format-ansi-codes (text)
(let ((result ()))
(while (> (length text) 0)
(cond
((starts-with-p text "\\e")
(push (code-char #o33) result)
(pop-chars text 2)
)
((starts-with-p text "\\r")
(push (code-char 13) result)
(pop-chars text 2)
)
(t (push (pop-first-char text) result))
))
(setf result (nreverse result))
(coerce result 'string)))
(defun kv-lookup (values key)
"Like getf, but works with 'keys as well as :keys, in both the list and the supplied key"
(setf key (if (typep key 'cons) (nth 1 key) key))
(while values
(let ((k (pop values)) (v (pop values)))
(setf k (if (typep k 'cons) (nth 1 k) k))
(if (eql (symbol-name key) (symbol-name k))
(return v)))))
(defun make-ansi-escape (ansi-name)
(let ((ansi-codes '( :normal "\\e[00m" :white "\\e[1;37m" :light-grey "\\e[0;37m" :dark-grey "\\e[1;30m"
:red "\\e[0;31m" :light-red "\\e[1;31m" :green "\\e[0;32m" :blue "\\e[1;34m" :dark-blue "\\e[1;34m"
:cyan "\\e[1;36m" :magenta "\\e[1;35m" :yellow "\\e[0;33m"
:bg-dark-grey "\\e[100m"
:bold "\\e[1m" :underline "\\e[4m"
:start-of-line "\\r" :clear-line "\\e[2K" :move-up "\\e[1A")))
(format-ansi-codes (kv-lookup ansi-codes ansi-name))
))
(defun format-ansi-escaped-arg (out-stream arg)
(cond
((typep arg 'symbol) (format out-stream "~a" (make-ansi-escape arg)))
((typep arg 'string) (format out-stream arg))
(t (format out-stream "~a" arg))
))
(defun format-ansi-escaped (out-stream &rest args)
(while args
(let ((arg (pop args)))
(if (typep arg 'list)
(let ((first-arg (eval (first arg))))
(format out-stream first-arg (second arg))
)
(format-ansi-escaped-arg out-stream arg)
))
))
(defmacro while-pop ((var sequence &optional result-form) &rest forms)
(with-gensyms (seq)
`(let (,var)
(progn
(do () ((not ,sequence))
(setf ,var (pop ,sequence))
(progn ,#forms))
,result-form))))
(defun report-start (form)
(format t "( ) '~a'~%" form))
(defun report-result (result form)
(format-ansi-escaped t "(" (if result :green :red) `("~:[FAIL~;PASS~]" ,result) :normal `(") '~a'~%" ,form))
result)
(defmacro ? (name &body body-forms)
"Run any number of test forms, optionally nested within further (?) calls, and print the results of each test"
(with-gensyms (result indent indent-string)
(if (not body-forms)
:empty
(progn
(setf result () indent 0 indent-string " ")
(cond
((typep (first body-forms) 'integer)
(setf indent (pop body-forms))))
`(progn
(format t "~v#{~A~:*~}" ,indent ,indent-string)
(format-ansi-escaped t "[" :white ,name :normal "]~%")
(with-gensyms (test-results)
(setf test-results ())
,(while-pop (body-form body-forms `(progn ,#(nreverse result)))
(cond
( (EQL (first body-form) '?)
(push `(progn
(setf test-results (append test-results (? ',(nth 1 body-form) ,(1+ indent) ,#(nthcdr 2 body-form))))
(format t "~%")
test-results
) result)
)
(t
(push `(progn
(format t "~v#{~A~:*~}" ,(1+ indent) ,indent-string)
(report-start ',body-form)
(with-gensyms (result label)
(setf result ,body-form)
(format-ansi-escaped t :move-up :start-of-line :clear-line)
(format t "~v#{~A~:*~}" ,(1+ indent) ,indent-string)
(push (report-result result ',body-form) test-results)
test-results
)) result))))))))))
(defun ?? (&rest results)
"Run any number of tests, and print a summary afterward"
(setf results (flatten results))
(format-ansi-escaped t "~&" :white "Results: " :green `("~a test~:p passed" ,(count t results)) :normal ", "
(if (find NIL results) :red :normal) `("~a test~:p failed" ,(count NIL results))
:yellow `("~[~:;, ~:*~a test~:p not run~]" ,(count :skip results))
:brown `("~[~:;, ~:*~a empty test group~:p skipped~]" ,(count :empty results))
:normal "~%"))
For my part, the ? macro is rather technical and it's hard to follow the logic behind the formatting functions. So instead of tracking errors I'd like to suggest my own attempt, perhaps it'll be of use.
I think that actually your ?? doesn't want to evaluate anything, but rather to treat its body as individual tests or sections. If the body includes a list starting with ?, this list represents a section; other elements are test forms optionally followed by descriptions. So in my implementation ?? will be a macro, and ? will be just a symbol.
I start with wishful thinking. I suppose I can create individual tests using a function make-test-item and test sections using a function make-test-section (their implementation is unimportant for now), that I can display them using an auxiliary function display-test and compute results using the function results, which returns two values: the total number of tests and the number of passed ones. Then I'd like the code
(??
(? "Arithmetic tests"
(? "Addition"
(= (+ 1 2) 3) "Adding 1 and 2 results in 3"
(= (+ 1 2 3) 6)
(= (+ -1 -3) 4))
(? "Subtraction"
(= (- 1 2) 1)))
(= (sin 0) 0) "Sine of 0 equals 0")
to expand into something like
(let ((tests (list (make-test-section :header "Arithmetic tests"
:items (list (make-test-section :header "Addition"
:items (list (make-test-item :form '(= (+ 1 2) 3)
:description "Adding 1 and 2 results in 3"
:passp (= (+ 1 2) 3))
(make-test-item :form '(= (+ 1 2 3) 6)
:passp (= (+ 1 2 3) 6))
(make-test-item :form '(= (+ -1 -3) 4)
:passp (= (+ -1 -3) 4))))
(make-test-section :header "Subtraction"
:items (list (make-test-item :form '(= (- 1 2) 1)
:passp (= (- 1 2) 1))))))
(make-test-item :form '(= (sin 0) 0)
:passp (= (sin 0) 0)
:description "Sine of 0 equals 0"))))
(loop for test in tests
with total = 0
with passed = 0
do (display-test test 0 t)
do (multiple-value-bind (ttl p) (results test)
(incf total ttl)
(incf passed p))
finally (display-result total passed t)))
Here a list of tests is created; then we traverse it printing each test (0 denotes the zero level of indentation and t is as in format) and keeping track of the results, finally displaying the total results. I don't think explicit eval is needed here.
It may not be the most exquisite piece of code ever, but it seems manageable. I supply missing definitions below, they are rather trivial (and can be improved) and have nothing to do with macros.
Now we pass on to the macros. Consider both pieces of code as data, then we want a list processing function which would turn the first one into the second. A few auxiliary functions would come in handy.
The major task is to parse the body of ?? and generate the list of test to go inside the let.
(defun test-item-form (form description)
`(make-test-item :form ',form :description ,description :passp ,form))
(defun test-section-form (header items)
`(make-test-section :header ,header :items (list ,#items)))
(defun parse-test (forms)
(let (new-forms)
(loop
(when (null forms)
(return (nreverse new-forms)))
(let ((f (pop forms)))
(cond ((and (listp f) (eq (first f) '?))
(push (test-section-form (second f) (parse-test (nthcdr 2 f))) new-forms))
((stringp (first forms))
(push (test-item-form f (pop forms)) new-forms))
(t (push (test-item-form f nil) new-forms)))))))
Here parse-test essentially absorbs the syntax of ??. Each iteration consumes one or two forms and collects corresponding make-... forms. The functions can be easily tested in REPL (and, of course, I did test them while writing).
Now the macro becomes quite simple:
(defmacro ?? (&body body)
`(let ((tests (list ,#(parse-test body))))
(loop for test in tests
with total = 0
with passed = 0
do (display-test test 0 t)
do (multiple-value-bind (ttl p) (results test)
(incf total ttl)
(incf passed p))
finally (display-result total passed t))))
It captures a few symbols, both in the variable name space and in the function one (the expansion may contain make-test-item and make-test-section). A clean solution with gensyms would be cumbersome, so I'd suggest just moving all the definitions in a separate package and exporting only ?? and ?.
For completeness, here is an implementation of the test API. Actually, it's what I started coding with and proceeded until I made sure the big let-form works; then I passed on to the macro part. This implementation is fairly sloppy; in particular, it doesn't support terminal colours and display-test can't even output a section into a string.
(defstruct test-item form description passp)
(defstruct test-section header items)
(defun results (test)
(etypecase test
(test-item (if (test-item-passp test)
(values 1 1)
(values 1 0)))
(test-section (let ((items-count 0)
(passed-count 0))
(dolist (i (test-section-items test) (values items-count passed-count))
(multiple-value-bind (i p) (results i)
(incf items-count i)
(incf passed-count p)))))))
(defparameter *test-indent* 2)
(defun display-test-item (i level stream)
(format stream "~V,0T~:[(FAIL)~;(PASS)~] ~:['~S'~;~:*~A~]~%"
(* level *test-indent*)
(test-item-passp i)
(test-item-description i)
(test-item-form i)))
(defun display-test-section-header (s level stream)
(format stream "~V,0T[~A]~%"
(* level *test-indent*)
(test-section-header s)))
(defun display-test (test level stream)
(etypecase test
(test-item (display-test-item test level stream))
(test-section
(display-test-section-header test level stream)
(dolist (i (test-section-items test))
(display-test i (1+ level) stream)))))
(defun display-result (total passed stream)
(format stream "Results: ~D test~:P passed, ~D test~:P failed.~%" passed (- total passed)))
All the code is licenced under WTFPL.
Today I received a reply to one of my emails in the form of a string of hex bytes:
"686170707920333974682068617665206120676f6f64206f6e6521"
And I was thinking of the most efficient clean way to convert the string into it's ASCII equivalent. I'll add my answer to the question but I didn't feel it was as elegant as it could have been.
Here's an iterative solution
(defun decode-hex-string (hex-string)
(let ((res nil))
(dotimes (i (/ (length hex-string) 2) (apply #'concat (reverse res)))
(let ((hex-byte (substring hex-string (* 2 i) (* 2 (+ i 1)))))
(push (format "%c" (string-to-number hex-byte 16)) res)))))
And one using loop, if you're looking to avoid side-effect operations (you may need to (require 'cl) in order to use this one):
(defun decode-hex-string (hex-string)
(apply #'concat
(loop for i from 0 to (- (/ (length hex-string) 2) 1)
for hex-byte = (substring hex-string (* 2 i) (* 2 (+ i 1)))
collect (format "%c" (string-to-number hex-byte 16)))))
In general, it's best to avoid recursion in Elisp and Common Lisp; your stack is going to keel over with a big enough input, and neither language guarantees tail recursion (which you aren't using, but still). In Scheme, it's a different story.
Incidentally, Happy 39th.
For those that come here searching...
Elaborating a bit on Inaimathi's answer, here's the code to replace the selected region with the decoded hexa:
(defun decode-hex-string (hex-string)
(apply #'concat
(loop for i from 0 to (- (/ (length hex-string) 2) 1)
for hex-byte = (substring hex-string (* 2 i) (* 2 (+ i 1)))
collect (format "%c" (string-to-number hex-byte 16)))))
(defun hex-decode-region (start end)
"Decode a hex string in the selected region."
(interactive "r")
(save-excursion
(let* ((decoded-text
(decode-hex-string
(buffer-substring start end))))
(delete-region start end)
(insert decoded-text))))
(provide 'decode-hex-string)
(provide 'hex-decode-region)
Save that on a file and then M-x load-file. Or put on ~/emacs.d, or whatever. Then select the region with the hexa contents and M-x hex-decode-region. Enjoy!
If you use Magnar Sveen's dash.el list API (and you should), try:
(concat (--map (string-to-number (concat it) 16) (-partition 2 (string-to-list "686170707920333974682068617665206120676f6f64206f6e6521"))))
the solution uses Emacs functions string-to-number, string-to-list and concat, and dash.el functions -partition and anaphoric version of -map. What's good about concat is that it concatenates not only strings, but lists or vectors of characters too. We can rewrite this code using ->> threading macro. It takes the result of 1st argument, then applies it to 2nd, 3rd, etc arguments, just like Unix pipe.
(->> (string-to-list "686170707920333974682068617665206120676f6f64206f6e6521")
(-partition 2)
(--map (string-to-number (concat it) 16))
concat)
Building the answers provided by Inaimathi and
Shrein, I also added an encode function. Here is an implementation of both encode and decode, for both string and region arguments:
;; ASCII-HEX converion
(defun my/hex-decode-string (hex-string)
(let ((res nil))
(dotimes (i (/ (length hex-string) 2) (apply #'concat (reverse res)))
(let ((hex-byte (substring hex-string (* 2 i) (* 2 (+ i 1)))))
(push (format "%c" (string-to-number hex-byte 16)) res)))))
(defun my/hex-encode-string (ascii-string)
(let ((res nil))
(dotimes (i (length ascii-string) (apply #'concat (reverse res)))
(let ((ascii-char (substring ascii-string i (+ i 1))))
(push (format "%x" (string-to-char ascii-char)) res)))))
(defun my/hex-decode-region (start end)
"Decode a hex string in the selected region."
(interactive "r")
(save-excursion
(let* ((decoded-text
(my/hex-decode-string
(buffer-substring start end))))
(delete-region start end)
(insert decoded-text))))
(defun my/hex-encode-region (start end)
"Encode a hex string in the selected region."
(interactive "r")
(save-excursion
(let* ((encoded-text
(my/hex-encode-string
(buffer-substring start end))))
(delete-region start end)
(insert encoded-text))))
Here's mine. I'm not claiming this is particularly idiomatic or elegant, either. Maybe a bit old-skool.
(defun hex-string-decode (str)
"Decode STR of the form \"4153434949\" to corresponding \"ASCII\"."
(let (decoded sub)
(while (> (length str) 0)
(setq sub (substring str 0 2)
decoded (cons (string-to-number sub 16) decoded)
str (substring str 2) ) )
(when (not (zerop (length str))) (error "residue %s" str))
(mapconcat #'char-to-string (nreverse decoded) "") ) )
At first I didn't see a requirement that it must be Elisp, so I did it interactively and the code below follows my interactive procedure.
(defun decode-hex-string (hex-string)
(with-temp-buffer
(insert-char 32 (/ (length hex-string) 2))
(beginning-of-buffer)
(hexl-mode)
(hexl-insert-hex-string hex-string 1)
(hexl-mode-exit)
(buffer-string)))
This was the solution I came up with which struck me as a bit ugly:
(defun decode-hex-string(string)
"Decode a hex string into ASCII"
(let* ((hex-byte (substring string 0 2))
(rest (substring string 2))
(rest-as-string (if (> (length rest) 2)
(decode-hex-string rest)
"")))
(format "%c%s" (string-to-number hex-byte 16) rest-as-string)))
I'm playing around in CL, making a One-Dimensional version of Battleship before I try to tackle a full Two-Dimensional version, and I've hit a hangup. To check if the boat is there, I've represented it with zeroes, and when a spot is hit, I replace it with an asterisk, so I can check the list with numberp. However, when I run (new-game), it immediately finishes, which tells me that I'm not entering the zeroes correctly so that they are recognized as numbers. What am I doing wrong? I know it must be a rookie mistake.
;;;; Suez-Canal.lisp
;;;;
;;;; A simple, 1-Dimensional version of Battleship
;;;; The computer places a boat randomly, and you must sink it.
(setf *random-state* (make-random-state t))
(defparameter *boat-length* 3)
(defparameter *canal-length* 10)
(defparameter *shots-fired* 0)
(defun new-game ()
(init-canal *canal-length*)
(place-boat)
(game-loop)
(format t "It took you ~a shots to sink the boat." *shots-fired*))
(defun init-canal (len)
(defparameter *canal* (make-list len)))
(defun place-boat ()
(let ((pos (random-spot)))
(setf (nth pos *canal*) 'O)
(setf (nth (+ pos 1) *canal*) 'O)
(setf (nth (+ pos 2) *canal*) 'O)))
(defun random-spot ()
(let ((x (random 7)))
x))
(defun game-loop ()
(loop until (notany #'numberp *canal*)
do (progn
(prompt-for-guess)
(check-guess (read-guess))
(incf *shots-fired*))))
(defun prompt-for-guess ()
(format t "~&Enter in a number between 1 and 10 to fire a shot.~&"))
(defun read-guess ()
(parse-integer (read-line *query-io*) :junk-allowed t))
(defun check-guess (guess)
(if (and (<= guess 9)
(>= guess 0))
(fire-shot guess)
(progn
(format t "~&Invalid selection~&")
(check-guess (read-guess)))))
(defun fire-shot (pos)
(if (= (nth (- pos 1) *canal*) 0)
(progn
(setf (nth (- pos 1) *canal*) #\*)
(print "Hit!"))
(print "Miss!")))
You are not entering zeroes at all, but rather the letter 'O'.
Other notes:
Do not use DEFPARAMETER inside DEFUN. Define the variable at top level, and inside the initialization function just SETF it.
Do not use lists for random access. Use arrays.
Numerical comparison operators will signal an error when given a non-numeric value. Use EQL for general comparisons.
Here is a corrected version:
(setf *random-state* (make-random-state t))
(defparameter *boat-length* 3)
(defparameter *canal-length* 10)
(defparameter *shots-fired* 0)
;;; you need to declare *canal* at toplevel.
(defparameter *canal* nil)
(defun new-game ()
(init-canal *canal-length*)
(place-boat)
(game-loop)
(format t "It took you ~a shots to sink the boat." *shots-fired*))
;;; just set the the variable.
(defun init-canal (length)
(setq *canal* (make-list length)))
;;; you need to set those positions to 0 and not to O
(defun place-boat ()
(let ((pos (random-spot)))
(setf (nth pos *canal*) 0)
(setf (nth (+ pos 1) *canal*) 0)
(setf (nth (+ pos 2) *canal*) 0)))
;;; no need for a LET
(defun random-spot ()
(random 7))
;;; no need for progn
;;; you could also replace UNTIL NOTANY with WHILE SOME
(defun game-loop ()
(loop until (notany #'numberp *canal*)
do
(prompt-for-guess)
(check-guess (read-guess))
(incf *shots-fired*)))
(defun prompt-for-guess ()
(format t "~&Enter in a number between 1 and 10 to fire a shot.~&"))
(defun read-guess ()
(parse-integer (read-line *query-io*) :junk-allowed t))
;;; <= can take more than two arguments
;;; typically this recursive version might be replaced with a LOOP
(defun check-guess (guess)
(if (<= 0 guess 9)
(fire-shot guess)
(progn
(format t "~&Invalid selection~&")
(check-guess (read-guess)))))
;;; use EQL, = only compares numbers
(defun fire-shot (pos)
(if (eql (nth (- pos 1) *canal*) 0)
(progn
(setf (nth (- pos 1) *canal*) #\*)
(print "Hit!"))
(print "Miss!")))