Clojure Exercism

Table of Contents

1. Number

1.1. Transfer

(bigdec 2)
(int 2.0)
(float 3)

1.2. Equal

(= 2 3)
(== 2.0 2)

1.3. String to Number

(type (read-string "233.3"))
(type (read-string "233"))

2. String

2.1. Preliminaries

Load clojure.string, with ns macro:

(ns aaa
  (:require [clojure.string :as str]))

or in the repl:

(require '[clojure.string :as str])

2.2. Basics

;; Size measurements
(count "0123")      ;=> 4
(empty? "0123")     ;=> false
(empty? "")         ;=> true
(str/blank? "    ") ;=> true

;; Concatenate
(str "foo" "bar")            ;=> "foobar"
(str/join ["0" "1" "2"])     ;=> "012"
(str/join "." ["0" "1" "2"]) ;=> "0.1.2"

;; Matching using plain Java methods.
;;
;; You might prefer regexes for these. For instance, failure returns
;; -1, which you have to test for. And characters like \o are
;; instances of java.lang.Character, which you may have to convert to
;; int or String.
(.indexOf "foo" "oo")         ;=> 1
(.indexOf "foo" "x")          ;=> -1
(.lastIndexOf "foo" (int \o)) ;=> 2

;; Substring
(subs "0123" 1)       ;=> "123"
(subs "0123" 1 3)     ;=> "12"
(str/trim "  foo  ")  ;=> "foo"
(str/triml "  foo  ") ;=> "foo  "
(str/trimr "  foo  ") ;=> "  foo"

;; Multiple substrings
(seq "foo")                       ;=> (\f \o \o)
(str/split "foo/bar/quux" #"/")   ;=> ["foo" "bar" "quux"]
(str/split "foo/bar/quux" #"/" 2) ;=> ["foo" "bar/quux"]
(str/split-lines "foo
bar")                             ;=> ["foo" "bar"]

;; Case
(str/lower-case "fOo") ;=> "foo"
(str/upper-case "fOo") ;=> "FOO"
(str/capitalize "fOo") ;=> "Foo"

;; Reverse
(str/reverse "hello") ;=> "olleh"

;; Escaping
(str/escape "foo|bar|quux" {\| "||"}) ;=> "foo||bar||quux"

;; Get byte array of given encoding.
;; (The output will likely have a different number than "3c3660".)
(.getBytes "foo" "UTF-8") ;=> #<byte[] [B@3c3660>

;; Parsing keywords
(keyword "foo")    ;=> :foo

;; Parsing numbers
(bigint "20000000000000000000000000000") ;=> 20000000000000000000000000000N
(bigdec "20000000000000000000.00000000") ;=> 20000000000000000000.00000000M
(Integer/parseInt "2")                   ;=> 2
(Float/parseFloat "2")                   ;=> 2.0

;; Parsing edn, a subset of Clojure forms.
(edn/read-string "0xffff") ;=> 65535

;; The sledgehammer approach to reading Clojure forms.
;;
;; SECURITY WARNING: Ensure *read-eval* is false when dealing with
;; strings you don't 100% trust. Even though *read-eval* is false by
;; default since Clojure 1.5, be paranoid and set it to false right
;; before you use it, because anything could've re-bound it to true.
(binding [*read-eval* false]
  (read-string "#\"[abc]\""))
;=> #"[abc]"

2.3. Matching

;; Simple matching
(re-find #"\d+" "foo 123 bar") ;=> "123"

;; What happens when a match fails.
(re-find #"\d+" "foobar") ;=> nil

;; Return only the first groups which satisfy match.
(re-matches #"(@\w+)\s([.0-9]+)%"
            "@shanley 19.8%")
;=>["@shanley 19.8%" "@shanley" "19.8"]

;; Return seq of all matching groups which occur in string.
(re-seq #"(@\w+)\s([.0-9]+)%"
        "@davidgraeber 12.3%,@shanley 19.8%")
;=> (["@davidgraeber 12.3%" "@davidgraeber" "12.3"]
;    ["@shanley 19.8%" "@shanley" "19.8"])

2.4. Replacing

;; In the replacement string, $0, $1, etc refer to matched groups.
(str/replace "@davidgraeber 12.3%,@shanley 19.8%"
             #"(@\S+)\s([.0-9]+)%"
             "$2 ($1)")
;=> "12.3 (@davidgraeber),19.8 (@shanley)"

;; Using a function to replace text gives us power.
(println
  (str/replace "@davidgraeber 12.3%,@shanley 19.8%"
               #"(@\w+)\s([.0-9]+)%,?"
               (fn [[_ person percent]]
                   (let [points (-> percent Float/parseFloat (* 100) Math/round)]
                     (str person "'s followers grew " points " points.\n")))))
;print=> @davidgraeber's followers grew 1230 points.
;print=> @shanley's followers grew 1980 points.
;print=>

2.5. Context-free grammars

;; Your project.clj should contain this (you may need to restart your JVM):
;;   :dependencies [[instaparse "1.2.4"]]
;;
;;  We'll assume your ns macro contains:
;;   (:require [instaparse.core :as insta])
;; or else in the repl you've loaded it:
;;   (require '[instaparse.core :as insta])

(def barely-tested-json-parser
  (insta/parser
   "object     = <'{'> <w*> (members <w*>)* <'}'>
    <members>  = pair (<w*> <','> <w*> members)*
    <pair>     = string <w*> <':'> <w*> value
    <value>    = string | number | object | array | 'true' | 'false' | 'null'
    array      = <'['> elements* <']'>
    <elements> = value <w*> (<','> <w*> elements)*
    number     = int frac? exp?
    <int>      = '-'? digits
    <frac>     = '.' digits
    <exp>      = e digits
    <e>        = ('e' | 'E') (<'+'> | '-')?
    <digits>   = #'[0-9]+'
    (* First sketched state machine; then it was easier to figure out
       regex syntax and all the maddening escape-backslashes. *)
    string     = <'\\\"'> #'([^\"\\\\]|\\\\.)*' <'\\\"'>
    <w>        = #'\\s+'"))

(barely-tested-json-parser "{\"foo\": {\"bar\": 99.9e-9, \"quux\": [1, 2, -3]}}")
;=> [:object
;     [:string "foo"]
;     [:object
;       [:string "bar"]
;       [:number "99" "." "9" "e" "-" "9"]
;       [:string "quux"]
;       [:array [:number "1"] [:number "2"] [:number "-" "3"]]]]

;; That last output is a bit verbose. Let's process it further.
(->> (barely-tested-json-parser "{\"foo\": {\"bar\": 99.9e-9, \"quux\": [1, 2, -3]}}")
     (insta/transform {:object hash-map
                       :string str
                       :array vector
                       :number (comp edn/read-string str)}))
;=> {"foo" {"quux" [1 2 -3], "bar" 9.99E-8}}


;; Now we can appreciate what those <angle-brackets> were all about.
;;
;; When to the right of the grammar's =, it totally hides the enclosed
;; thing in the output. For example, we don't care about whitespace,
;; so we hide it with <w*>.
;;
;; When to the left of the grammar's =, it merely prevents a level of
;; nesting in the output. For example, "members" is a rather
;; artificial entity, so we prevent a pointless level of nesting with
;; <members>.

2.6. Building complex strings

2.6.1. Redirecting streams

with-out-str provides a simple way to build strings. It redirects standard output (*out*) to a fresh StringWriter, then returns the resulting string. So you can use functions like print, even in nested functions, and get the resulting string at the end.

(let [shrimp-varieties ["shrimp-kabobs" "shrimp creole" "shrimp gumbo"]]
  (with-out-str
    (print "We have ")
    (doseq [name (str/join ", " shrimp-varieties)]
      (print name))
    (print "...")))
;=> "We have shrimp-kabobs, shrimp creole, shrimp gumbo..."

2.6.2. Format strings

;; %s is most commonly used to print args. Escape %'s with %%.
(format "%s enjoyed %s%%." "Mozambique" 19.8) ;=> "Mozambique enjoyed 19.8%."

;; The 1$ prefix allows you to keep referring to the first arg.
(format "%1$tY-%1$tm-%1$td" #inst"2000-01-02T00:00:00") ;=> "2000-01-02"

;; Again, 1$, 2$, etc prefixes let us refer to args in arbitrary orders.
(format "New year: %2$tY. Old year: %1$tY"
        #inst"2000-01-02T00:00:00"
        #inst"3111-12-31T00:00:00")
;=> "New year: 3111. Old year: 2000"

3. Collection

(filter #(not= 1) '(2 1 1 3))                 ; => (2 3)
(sort "hello")                                ; => (\e \h \l \l \o)
(let [f (partial = 1)] (f 1))                 ; => true
(frequencies "hello")                         ; => {\h 1, \e 1, \l 2, \o 1}
(filter (every-pred even? pos?) (range -4 5)) ; => (2 4)

4. Flow

4.1. condp

(defn question? [sentence]
  (= \? (last sentence)))

(defn yell? [sentence]
  (and (re-find #"[A-Z]" sentence)
       (not (re-find #"[a-z]" sentence))))

(defn response-for [s]
  (condp apply [(trim s)]
    blank? "Fine. Be that way!"
    (every-pred question? yell?) "Calm down, I know what I'm doing!"
    question? "Sure."
    yell? "Whoa, chill out!"
    "Whatever."))