user477768
user477768

Reputation:

Make String from Sequence of Characters

This code does not work as I expected. Could you please explain why?

(defn make-str [s c]
    (let [my-str (ref s)]
    (dosync (alter my-str str c))))

(defn make-str-from-chars
    "make a string from a sequence of characters"
    ([chars] make-str-from-chars chars "")
    ([chars result]
        (if (== (count chars) 0) result
        (recur (drop 1 chars) (make-str result (take 1 chars))))))

Thank you!

Upvotes: 5

Views: 4388

Answers (3)

Alan Thompson
Alan Thompson

Reputation: 29958

You can also use clojure.string/join, as follows:

(require '[clojure.string :as str] )
(assert (= (vec "abcd")                [\a \b \c \d] ))
(assert (= (str/join  (vec "abcd"))    "abcd" ))

There is an alternate form of clojure.string/join which accepts a separator. See:

http://clojuredocs.org/clojure_core/clojure.string/join

Upvotes: 2

kotarak
kotarak

Reputation: 17299

You pass a sequence with one character in it to your make-str function, not the character itself. Using first instead of take should give you the desired effect.

Also there is no need to use references. In effect your use of them is a gross misuse of them. You already use an accumulator in your function, so you can use str directly.

(defn make-str-from-chars
  "make a string from a sequence of characters"
  ([chars] (make-str-from-chars chars ""))
  ([chars result]
    (if (zero? (count chars))
      result
      (recur (drop 1 chars) (str result (first chars))))))

Of course count is not very nice in this case, because it always has to walk the whole sequence to figure out its length. So you traverse the input sequence several times unnecessarily. One normally uses seq to identify when a sequence is exhausted. We can also use next instead of drop to save some overhead of creating unnecessary sequence objects. Be sure to capture the return value of seq to avoid overhead of object creations later on. We do this in the if-let.

(defn make-str-from-chars
  "make a string from a sequence of characters"
  ([chars] (make-str-from-chars chars ""))
  ([chars result]
     (if-let [chars (seq chars)]
       (recur (next chars) (str result (first chars)))
       result)))

Functions like this, which just return the accumulator upon fully consuming its input, cry for reduce.

(defn make-str-from-chars
  "make a string from a sequence of characters"
  [chars]
  (reduce str "" chars))

This is already nice and short, but in this particular case we can do even a little better by using apply. Then str can use the underlying StringBuilder to its full power.

(defn make-str-from-chars
  "make a string from a sequence of characters"
  [chars]
  (apply str chars))

Hope this helps.

Upvotes: 11

Alex Ott
Alex Ott

Reputation: 87119

This is very slow & incorrect way to create string from seq of characters. The main problem, that changes aren't propagated - ref creates new reference to existing string, but after it exits from function, reference is destroyed.

The correct way to do this is:

(apply str seq)

for example,

 user=> (apply str [\1 \2 \3 \4])
 "1234"

If you want to make it more effective, then you can use Java's StringBuilder to collect all data in string. (Strings in Java are also immutable)

Upvotes: 12

Related Questions