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."))