From googling around, I found that using while
loops or using variables is discouraged.
Now I implemented a very simple algorithm that will read characters from an inputstream and parse accordingly: if input is 10:abcdefghej
it will parse out 10
then read next 10 bytes after the colon.
The thing I am kinda lost with is how I can refactor this so it does not rely on variables.
(defn decode-string [input-stream indicator]
(with-local-vars [length (str (char indicator) )
delimiter (.read input-stream )
string (str "")
counter 0 ]
(while (not(= (var-get delimiter) 58 ))
(var-set length (str (var-get length) (char (var-get delimiter)) ))
(var-set delimiter (.read input-stream )))
(var-set length (new BigInteger (var-get length)) )
(var-set counter (var-get length))
(while (not(zero? (var-get counter) ))
(var-set string (str (var-get string) (char (.read input-stream )) ))
(var-set counter (dec (var-get counter))))
(var-get string)))
Also, I understand that the only way to declare variables is using the with-local-vars
keyword. Isn't it kind of unpractical to define all the variables in one block at the beginning, or am I missing some crucial point?
What you are writing is C code with lisp-like syntax (no offense intended). Defining a style by what you don't do is very defining, but it is not very helpful if you don't know "well, then how else?"
By the way, I don't know what indicator
is supposed to do.
This is how I would approach this problem:
The problem has two parts: find the number of characters to read, then read that many characters. Therefore, I would write two functions: read-count
and read-item
, the latter using the former.
(defn read-count [stream] ;; todo ) (defn read-item [stream] ;; todo )
read-item
first needs to determine the number of characters to read. For that, it uses the convenient function read-count
that we will also define.
(defn read-item [stream] (let [count (read-count stream)] ;; todo ))
Looping is in Clojure generally best handled by using loop
and recur
. loop
also binds variables, like let
. acc
is meant to accumulate the read items, but note that it is not modified in place but re-bound each iteration.
(defn read-item [stream] (loop [count (read-count stream) acc ""] ;; todo (recur (dec count) ; new value for count (str acc c))))) ; new value for acc
Now we need to do something in that loop: bind c
to the next character, but return acc
when count
is 0. (zero? count)
is the same as (= count 0)
. I annotated the if
form a bit for those unfamiliar with it.
(defn read-item [stream] (loop [count (read-count stream) acc ""] (if (zero? count) ; condition acc ; then (let [c (.read stream)] ; \ (recur (dec count) ; > else (str acc c))))))) ; /
Now all we need is the read-count
function. It uses a similar loop.
(defn read-count [stream] (loop [count 0] (let [c (.read stream)] (if (= c ":") count (recur (+ (* count 10) (Integer/parseInt c)))))))
Test it on the REPL, debug, refactor. Does .read
really return characters? Is there a better way to parse an integer?
I have not tested this, and I am a bit hampered by not having any experience nor deep knowledge of Clojure (I use Common Lisp mostly), but I think that it shows how to approach this kind of problem in a "lispy" way. Note how I don't think about declaring or modifying variables.
A bit late to this party, I suppose, but the problem is much simpler if you just treat the string as a sequence of characters and use Clojure's sequence-handling primitives:
(defn read-prefixed-string [stream]
(let [s (repeatedly #(char (.read stream)))
[before [colon & after]] (split-with (complement #{\:}) s)
num-chars (read-string (apply str before))]
(apply str (take num-chars after))))
user> (let [in (java.io.StringReader. "10:abcdefghij5:klmnopqrstuvwxyz")]
(repeatedly 2 #(read-prefixed-string in)))
("abcdefghij" "klmno")
A summary:
before
and after
, and strip out the :
while we are at it, by binding it to an unused local, named colon
for descriptiveness.before
to get its numerical valueafter
, and mash them all together into a string with (apply str)
Svante's answer is an excellent example of how to write loop-ish code with Clojure; I hope that mine is a good example of assembling the built-in functions so that they do what you need. Certainly both of these make the C solution look anything but "very simple"!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With