Idiomatic Way to Build String or Simply Use StringBuilder

2,116 views
Skip to first unread message

HiHeelHottie

unread,
Sep 29, 2010, 10:48:19 PM9/29/10
to Clojure

Is there an idiomatic way to build up a string over different lines of
code? Or, should one simply use StringBuilder.

Stuart Campbell

unread,
Sep 29, 2010, 11:32:12 PM9/29/10
to clo...@googlegroups.com
On 30 September 2010 12:48, HiHeelHottie <hiheel...@gmail.com> wrote:

Is there an idiomatic way to build up a string over different lines of
code?  Or, should one simply use StringBuilder.


I would just use (str) - it uses a StringBuilder when given more than one argument:

user> (source str)
(defn str
  "With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args."
  {:tag String
   :added "1.0"}
  ([] "")
  ([^Object x]
   (if (nil? x) "" (. x (toString))))
  ([x & ys]
     ((fn [^StringBuilder sb more]
          (if more
            (recur (. sb  (append (str (first more)))) (next more))
            (str sb)))
      (new StringBuilder ^String (str x)) ys)))

Regards,
Stuart

Michael Gardner

unread,
Sep 29, 2010, 11:58:40 PM9/29/10
to clo...@googlegroups.com
On Sep 29, 2010, at 10:32 PM, Stuart Campbell wrote:

> I would just use (str) - it uses a StringBuilder when given more than one argument:

There's also (format), which I find helpful for building more complex strings.

HiHeelHottie

unread,
Sep 30, 2010, 12:01:20 AM9/30/10
to Clojure

Thanks for the response. What if you are appending over different
lines of code? Would it be slightly more efficient to use one
StringBuilder or not worth the bother.

On Sep 29, 11:32 pm, Stuart Campbell <stu...@harto.org> wrote:

Michael Gardner

unread,
Sep 30, 2010, 12:15:49 AM9/30/10
to clo...@googlegroups.com
On Sep 29, 2010, at 11:01 PM, HiHeelHottie wrote:

> What if you are appending over different lines of code?

Could you give an example of what you're trying to do? Mutable strings are almost never necessary, in my experience.

Mark Engelberg

unread,
Sep 30, 2010, 12:42:40 AM9/30/10
to clo...@googlegroups.com
Start with an empty vector, say v.
conj your strings to the vector at the various points in your code, so
at the end v will be something like
["this" "is" "a" "string"]
Then, when you're done, apply str to the vector, i.e., (apply str v) to get
"thisisastring"

str uses a string builder behind the scenes, so it's efficient this
way. If you keep applying str at each point in your code, it won't
be.

Sean Corfield

unread,
Sep 30, 2010, 12:42:51 AM9/30/10
to clo...@googlegroups.com
On Wed, Sep 29, 2010 at 9:01 PM, HiHeelHottie <hiheel...@gmail.com> wrote:
> Thanks for the response.  What if you are appending over different
> lines of code?  Would it be slightly more efficient to use one
> StringBuilder or not worth the bother.

I'm trying to think what your code would look like that you'd have
multiple 'lines' computing pieces that you are assembling into a
string section at a time...?

If you're accumulating things that you want to turn into a string
later, you could always put the pieces into a vector and then do:

(apply str vector-of-pieces)
--
Sean A Corfield -- (904) 302-SEAN
Railo Technologies, Inc. -- http://getrailo.com/
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

B Smith-Mannschott

unread,
Sep 30, 2010, 1:28:26 AM9/30/10
to clo...@googlegroups.com
On Thu, Sep 30, 2010 at 04:48, HiHeelHottie <hiheel...@gmail.com> wrote:

Is there an idiomatic way to build up a string over different lines of
code?  Or, should one simply use StringBuilder.


I recently wrote a program that generates complex java enums (as source) from input data recorded in clojure syntax (by a VBA macro running in excel ...).

As java is syntactically correct, I quickly found that it was smart of break up the algorithm into various functions that called each other.

A few examples (sketeched out from memory):

(defn enum-item-init [name args]
  [name "(" (interpose ", " (map enum-format-arg)) ")"])

(defn enum-items [item-map]
  [(interpose "," (map (fn [[name args]] (enum-item-init name args)) item-map)) ";"])

(defn enum-class [name item-map]
  ["public enum " name " {\n" (enum-items item-map) "\n}\n"])

I also used Clojure's multi-line string literals to good effect, though that's now shown here.

So, really I'd invented a sort of poor-man's templating language within Clojure.

So, I have each function return a sequence of strings. Some return vectors, some return lazy sequences resulting from list comprehensions (for [...] ...). In the end, the top-level function returns a sort of seq of seq of seq ... of strings. So, a tree of strings really.

This worked well for me as a way of decomposing my program. Here's the punch-line:

(apply str (flatten (enum-class name item-map)))

// Ben

Steven E. Harris

unread,
Sep 30, 2010, 8:07:06 PM9/30/10
to clo...@googlegroups.com
Mark Engelberg <mark.en...@gmail.com> writes:

> str uses a string builder behind the scenes, so it's efficient this
> way.

If the `str' implementation didn't take the input sequence to be lazy,
it could figure out how long the resulting string needed to be, and
construct the StringBuilder using the single-integer constructor,
ensuring that no reallocation and copying occurs. Some temporary
allocation would still be necessary to hold the Object-to-String
projection, as `str' calls Object#toString() on each argument, rather
than assuming the arguments are already of type String.

--
Steven E. Harris

HiHeelHottie

unread,
Sep 30, 2010, 11:37:00 PM9/30/10
to Clojure

Everybody, thanks for all your responses. conj to vector feels good
so that's what I'm playing with now. Michael, in answer to your
question, and this may be more detail than you bargained for, I'm
playing around with a little state machine parser. It actually
doesn't do much now, but baby steps as I learn clojure. Eventually, I
want it to make distinctions between numbers that pass through and
numbers that are transformed. Am not crazy about having to add a
space at the end of the original string and welcome edifying comments.

(ns test-test.parse
(:use [clojure.contrib.string :only (split)]))

(defn parse-char [m c]
(condp = (:state m)
:degree (cond
(Character/isDigit c) (assoc m :degree (+ (* (:degree
m) 10) (Character/digit c 10)))
(Character/isWhitespace c) (assoc
m :state :whitespace :buf (conj (:buf m) (:degree m) " ") :degree 0))
:whitespace (cond
(Character/isDigit c) (assoc
m :state :degree :degree (+ (* (:degree m) 10) (Character/digit c
10)))
(Character/isWhitespace c) m)))

(defn parse [s]
(let [m (reduce parse-char {:state :degree :degree 0 :buf []} (str s
" "))]
(apply str (:buf m))))

(println (parse "1 2 33"))

Michael Gardner

unread,
Oct 1, 2010, 12:58:46 PM10/1/10
to clo...@googlegroups.com
On Sep 30, 2010, at 10:37 PM, HiHeelHottie wrote:

> (ns test-test.parse
> (:use [clojure.contrib.string :only (split)]))
>
> (defn parse-char [m c]
> (condp = (:state m)
> :degree (cond
> (Character/isDigit c) (assoc m :degree (+ (* (:degree
> m) 10) (Character/digit c 10)))
> (Character/isWhitespace c) (assoc
> m :state :whitespace :buf (conj (:buf m) (:degree m) " ") :degree 0))
> :whitespace (cond
> (Character/isDigit c) (assoc
> m :state :degree :degree (+ (* (:degree m) 10) (Character/digit c
> 10)))
> (Character/isWhitespace c) m)))
>
> (defn parse [s]
> (let [m (reduce parse-char {:state :degree :degree 0 :buf []} (str s
> " "))]
> (apply str (:buf m))))
>
> (println (parse "1 2 33"))

One minor improvement would be to use clojure.string/join instead of str. That way you won't have to manually append a space after each number in :buf (nor use apply).

Reply all
Reply to author
Forward
0 new messages