Clojure Variables and Looping
clojure, lisp, loops, variables
Solution
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:
- Convert the ugly, side-effectful input stream into a lazy sequence of characters, so that we can pretend it's just a string for the rest of this operation. As you can see, no more characters are actually read from the stream than are necessary to compute the result.
- Split the string into two parts: the first half characters before the first colon, and the second half whatever is left.
- Use destructuring to bind those parts to locals named `before` and `after`, and strip out the `:` while we are at it, by binding it to an unused local, named `colon` for descriptiveness.
- Read `before` to get its numerical value
- Take that many characters from `after`, 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"!
Problem
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?