0

map and stringify

I have this piece of clojure code:

(def up (memfn toUpperCase))   
(str "\n" (apply str (interpose "\n" (map up '("one" "two")))) "\n"); "\nONE\nTWO\n"

The code does exactly what it is supposed to do: take a list of string, uppercase each one and envelop each one with a \n (inc before and after).

But there must be a way to write this in a more elegant way. Please help.

viebel
  • 19,372
  • 10
  • 49
  • 83

5 Answers5

4

you could combine the map and interpose:

(apply str "\n" (map #(str (up %) "\n") '("one" "two")))

also, not necessarily more elegant, bit in the spirit of timtowdi:

(clojure.pprint/cl-format false "~%~{~:@(~A~)~%~}" '("one" "two"))

see practical common lisp for a tutorial on cl format strings.

Martin DeMello
  • 11,876
  • 7
  • 49
  • 64
  • Nice. But I guess that there should be an even more elegant way to interpose with header and footer. What do you think? – viebel Feb 14 '12 at 22:08
  • the problem is you're not interposing, you're "outerposing", so to speak. – Martin DeMello Feb 14 '12 at 22:17
  • Yes! *“In the elder days there would have been at least a few followups showing how to do this in the proper "FORMAT string indistinguishable from line noise" way.”* —[Tim Bradshaw, comp.lang.lisp](https://groups.google.com/group/comp.lang.lisp/msg/54beeed56ec2c5de) – Matthias Benkard Feb 14 '12 at 22:39
4

I actually quite like the with-out-str approach to this sort of thing:

(with-out-str
  (println)
  (doseq [s ["one" "two"]]
    (println (.toUpperCase ^String s))))

It seems to be about 2-3x slower than your original approach and Martin's "combined map and interpose" variant with type hints added (and ~30x faster than cl-format, which however clearly wins on the coolness factor :-)). (See end of this answer for a note on hinting & reflection.)

Another version just to keep up the timtowtdi spirit: for the ultimate in speed (up to ~2x speedup over your original version), should you have reason to care about that, you could use

(loop [sb (doto (StringBuilder.)
            (.append \newline))
       strs ["one" "two"]]
  (if-let [s (first strs)]
    (do (.append sb (.toUpperCase ^String s))
        (.append sb \newline)
        (recur sb (next strs)))
    (.toString sb)))))

Somewhat tangentially to the main question, I timed all approaches after getting rid of all reflection warnings; in particular, I used

(def up #(.toUpperCase ^String %))

(Incidentally, #(.foo %) seems to be used much more often than memfn even when no type hints are specified.)

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • The problem with `#(.foo %)` is that you have to worry manually with the number of args while this is not the case with `memfn`. – viebel Feb 15 '12 at 07:46
  • That's not true, you need to say something like `(memfn foo a b c)` (using arbitrary symbols in place of `a b c`, as long as they are pairwise different) to create a wrapper for a method taking three arguments. See `(source memfn)` for details. – Michał Marczyk Feb 15 '12 at 07:55
1

I came up with:

 (defn interpose-envelop-and-stringify [coll sep]
   (str sep
        (join sep coll)
        sep))
 (interpose-envelop-and-stringify (map up ["one" "two"]) "\n")

I am using join from clojure.string.

liwp
  • 6,746
  • 1
  • 27
  • 39
viebel
  • 19,372
  • 10
  • 49
  • 83
0

You do the right thing. One advise - use function arg or let form to define separator, in that case when you need change separator you change it only in one place (not 3 as in your case)

(defn stringify [sq sep]
  (reduce str sep (map #(str (.toUpperCase %) sep) sq)))

(stringify ["one" "two"] "\n") => "\nONE\nTWO\n"
mishadoff
  • 10,719
  • 2
  • 33
  • 55
  • 3
    One note: `str` works best with `apply` (rather than `reduce`) -- it uses a `StringBuilder` internally when called with multiple arguments, so it's better to let it handle its own looping. (It's an instance of a more general pattern which I discussed in [another answer](http://stackoverflow.com/a/3153643/232707) some time ago.) – Michał Marczyk Feb 15 '12 at 00:08
  • Thanks! I perform some tests apply str vs reduce str, and for 100k strings apply much better. In other way reduce + slightly better than apply +. (I read your comment to related question, makes sense) – mishadoff Feb 15 '12 at 09:09
0

The thrush operation can make things reasonably clean, but there's still an enclosing apply that's needed to join the list into one big string.

(defn stringify [s] (apply str "\n" (map #(-> % .toUpperCase (str "\n")) s)))

(stringify '("one" "two")) ; yields "\nONE\nTWO\n"
sandover
  • 7,488
  • 1
  • 18
  • 11