So far, we've made a function that tells us the type of a given character:
(def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) (def char-names {\space :space \. :period \/ :forward-slash \" :double-quote}) (defn char-type [ch] (cond (contains? digits ch) :digit (contains? char-names ch) (get char-names ch) true :other))
Then, we looped over an array of characters and grouped them according to their type:
(def chars (vec "42 0.5 1/2 foo-bar")) (loop [result [] index 0 last-type nil group nil] (let [ch (get chars index) new-type (char-type ch) new-index (+ index 1)] (if ch (if (= last-type new-type) (recur result new-index last-type (conj group ch)) (let [new-result (if group (conj result group) result)] (recur new-result new-index new-type [ch]))) (conj result group))))
We now want to combine these grouped characters into strings. One way to do that is with the str function:
(str \h \e \l \l \o)
So now we can change our loop to store the groups as strings rather than in vectors by simply changing how the group is passed to recur:
(loop [result [] index 0 last-type nil group nil] (let [ch (get chars index) new-type (char-type ch) new-index (+ index 1)] (if ch (if (= last-type new-type) (recur result new-index last-type (str group ch)) (let [new-result (if group (conj result group) result)] (recur new-result new-index new-type (str ch)))) (conj result group))))
While this is a good start, it's not quite what we want. A reader needs to treat 1.5 as a discrete number, so instead of ["0" "." "5"] we really just want "0.5". To do this, let's make a more sophisticated function for deciding if the types are the same:
(defn same-type? [left-type right-type] (or (= left-type right-type) (= [left-type right-type] [:digit :period])))
Then use it where we are currently are doing (= last-type new-type):
(loop [result [] index 0 last-type nil group nil] (let [ch (get chars index) new-type (char-type ch) new-index (+ index 1)] (if ch (if (same-type? last-type new-type) (recur result new-index last-type (str group ch)) (let [new-result (if group (conj result group) result)] (recur new-result new-index new-type (str ch)))) (conj result group))))
Now our 0.5 is merged together! Let's finally put this loop in a function so we can easily call it:
(defn partition-chars [chars] (loop [result [] index 0 last-type nil group nil] (let [ch (get chars index) new-type (char-type ch) new-index (+ index 1)] (if ch (if (same-type? last-type new-type) (recur result new-index last-type (str group ch)) (let [new-result (if group (conj result group) result)] (recur new-result new-index new-type (str ch)))) (conj result group)))))
Now we can call it like this:
(partition-chars chars)
Notice that the function argument is called chars. It is perfectly fine for a var and a function argument to have the same name. Inside the function, the evaluator will resolve chars as the argument. Outside the function, chars will resolve to the var as usual. This is called shadowing.
Now we can easily start merging other things. Let's make fractions merge together:
(defn same-type? [left-type right-type] (or (= left-type right-type) (= [left-type right-type] [:digit :period]) (= [left-type right-type] [:digit :forward-slash])))
And now 1/2 is a single string:
(partition-chars chars)
There's a simpler way to write this. We can put all "merge pairs" in a set:
(def merge-pairs #{[:digit :period] [:digit :forward-slash]})
And then make same-type? check if the current pair is in it:
(defn same-type? [left-type right-type] (or (= left-type right-type) (contains? merge-pairs [left-type right-type])))
It should still work the same way:
Previous: LoopsNext: Regular Expressions(partition-chars chars)