How can I convert a LazySeq of Characters to a String in Clojure?

MapClojureTypesPrimitive TypesCoercion

Map Problem Overview


Let's say I have a LazySeq of java.lang.Character like

(\b \ \! \/ \b \ \% \1 \9 \/ \. \i \% \$ \i \space \^@)

How can I convert this to a String? I've tried the obvious

(String. my-char-seq)

but it throws

java.lang.IllegalArgumentException: No matching ctor found for class java.lang.String (NO_SOURCE_FILE:0)
[Thrown class clojure.lang.Compiler$CompilerException]

I think because the String constructor expects a primitive char[] instead of a LazySeq. So then I tried something like

(String. (into-array my-char-seq))

but it throws the same exception. The problem now is that into-array is returning a java.lang.Character[] instead of a primitive char[]. This is frustrating, because I actually generate my character sequence like this

(map #(char (Integer. %)) seq-of-ascii-ints)

Basically I have a seq of ints representing ASCII characters; 65 = A, etc. You can see I explicitly use the primitive type coercion function (char x).

What this means is that my map function is returning a primitive char but the Clojure map function overall is returning the java.lang.Character object.

Map Solutions


Solution 1 - Map

This works:

(apply str my-char-seq)

Basically, str calls toString() on each of its args and then concatenates them. Here we are using apply to pass the characters in the sequence as args to str.

Solution 2 - Map

Another way is to use clojure.string/join, as follows:

(require '[clojure.string :as str] )
(assert (= (vec "abcd")                [\a \b \c \d] ))
(assert (= (str/join  (vec "abcd"))    "abcd" ))
(assert (= (apply str (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

For more complicated problems, you may also wish to lookat strcat from the Tupelo library:

(require '[tupelo.core :as t] )
(prn (t/strcat "I " [ \h \a nil \v [\e \space (byte-array [97])
                  [ nil 32 "complicated" (Math/pow 2 5) '( "str" nil "ing") ]]] ))
;=> "I have a complicated string"

Solution 3 - Map

As a special case, if the underlying type of the sequence in question is clojure.lang.StringSeq you can also do:

(.s (my-seq))

which is extremely performant as it is just pulling out the public final CharSequence field from the clojure StringSeq class.

Example:

(type (seq "foo"))
=> clojure.lang.StringSeq

(.s (seq "foo"))
=> "foo"

(type (.s (seq "foo")))
=> java.lang.String

an example of the timing implications (and note the difference when using a type hint):

(time 
  (let [q (seq "xxxxxxxxxxxxxxxxxxxx")]
    (dotimes [_ 1000000]
      (apply str q))))
"Elapsed time: 620.943971 msecs"
=> nil

(time 
  (let [q (seq "xxxxxxxxxxxxxxxxxxxx")]
    (dotimes [_ 1000000]
      (.s q))))
"Elapsed time: 1232.119319 msecs"
=> nil

(time 
  (let [^StringSeq q (seq "xxxxxxxxxxxxxxxxxxxx")]
    (dotimes [_ 1000000]
      (.s q))))
"Elapsed time: 3.339613 msecs"
=> nil

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRobert CampbellView Question on Stackoverflow
Solution 1 - MapSiddhartha ReddyView Answer on Stackoverflow
Solution 2 - MapAlan ThompsonView Answer on Stackoverflow
Solution 3 - MapMatias BjarlandView Answer on Stackoverflow