Thursday, 24 November 2016

How to convert from kebab-case to camelCase in Clojure

Here are six functions that do that. Pick your favourite. It won't be the best performing one. Can you guess which that is? Scroll down for answer.
(defn kebab->camel1 [^String kebab]
  (->> kebab
       (reduce
        (fn [[buf ucase] c]
          (let [dash (= \- c)]
            [(if dash
               buf
               (conj buf (if ucase
                           (Character/toUpperCase c)
                           c)))
             dash]))
        [[] false])
       first
       (apply str)))

(defn kebab->camel2 [^String kebab]
  (->> (map
        (fn [cur prev] (if (= \- prev)
                         (Character/toUpperCase cur)
                         cur)) kebab (cons nil kebab))
       (filter #(not= \- %))
       (apply str)))

(defn kebab->camel3 [^String kebab]
  (loop [buf []
         ucase false
         rst kebab]
    (if (seq rst)
      (let [c (first rst)
            dash (= \- c)]
        (recur (if dash
                 buf
                 (conj buf (if ucase
                             (Character/toUpperCase c)
                             c)))
               dash
               (rest rst)))
      (apply str buf))))

(defn kebab->camel4 [^String kebab]
  (->> (map
        (fn [[prev cur]] (if (= \- prev)
                           (Character/toUpperCase cur)
                           cur)) (partition 2 1 (cons nil kebab)))

       (filter #(not= \- %))
       (apply str)))

(defn kebab->camel5 [^String kebab]
  (let [pv (volatile! nil)
        sb (StringBuilder. (count kebab))]
    (doseq [c kebab]
      (let [prior @pv
            dash (= \- c)]
        (vreset! pv dash)
        (when-not dash (.append
                        sb
                        (if prior
                          (Character/toUpperCase c) c)))))
    (.toString sb)))

(defn kebab->camel6 [^String kebab]
  (let [buf (.toCharArray kebab)
        len (count buf)]
    (loop [src 0 dst 0 ucase nil]
      (if (< src len)
        (let [c (aget buf src)
              dash (= \- c)]
          (when-not dash (aset buf dst (if ucase (Character/toUpperCase c) c)))
          (recur (inc src) (if dash dst (inc dst)) dash))
        (String. buf 0 dst)))))

To see which is best we do this

(let [kebab (->> (.ints (java.util.Random. 17)  0 31)
                 .iterator
                 iterator-seq
                 dedupe
                 (map #(nth "abcdefghijklmnopqrstuvwxyz-----" %))
                 (take 10000)
                 (apply str))
      time-fn (fn [f]
                (let [start (System/nanoTime)]
                  (f)
                  (/ (double (- (System/nanoTime) start)) 1000000.0)))]
  (for [f [kebab->camel1
           kebab->camel2
           kebab->camel3
           kebab->camel4
           kebab->camel5
           kebab->camel6]]
    [f (time-fn #(dotimes [_ 1000] (f kebab)))]))

and get results like these

([#function[user/kebab->camel1] 9064.630932]
 [#function[user/kebab->camel2] 11520.793361]
 [#function[user/kebab->camel3] 8440.000948]
 [#function[user/kebab->camel4] 20740.882337]
 [#function[user/kebab->camel5] 8455.671506]
 [#function[user/kebab->camel6] 99.724767])

No comments:

Post a Comment