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