Clojure SnippetsDezember 2020
Beispiele aus dem Buch
Programming Clojure von Miller, Halloway, Bedra und von der Seite
Brave Clojure.
(ns test.core)
; Comments with ;
(comment "Comments out blocks")
1000M ; BigDecimal
1000N ; BigInt
[1 2 3] ; Vector
'(1 2 3) ; List
#{1 2 3} ; Set
{:a 1 :b 2} ; Map
(str 1 2 3) ; 123 - Convert to String and concatenate
(true? true) ; true
(false? true) ; false
(nil? nil) ; true
(zero? 1) ; false
(string? "Hello") ; true
(keyword? :hello) ; true
(symbol? 'hello) ; true
(defn myfunc
"description"
[arg1 arg2]
(str arg1 arg2))
(fn [arg1 arg2] (str arg1 arg2)) ; anonymous function
#(str %1 %2) ; anonymous function
; also see letfn-macro below
(def foo 10) ; create var
(var foo) ; returns var
#'foo ; returns var
(let [x "1"
y "2"]
(str x y)) ; create lexical bindings
; destructuring
(defn greet-2
[{fname :first-name}]
(println "Hello," fname))
(greet-2 {:first-name "Hans" :last-name "Test"})
; more destructuring
(let [[x y] [1 2 3]] [x y]) ; [1 2]
(let [[_ _ z] [1 2 3]] z) ; 3
(let [[x y :as coords] [1 2 3 4 5 6]]
(str x y " total: " (count coords))) ; binds coords to collection: "12 total: 6
(in-ns 'myapp) ; switch to namespace myapp
(clojure.core/use 'clojure.core) ; use clojure core namespace
(ns examples.coloring ; set namespace to examples.coloring, macro supports :require, :import, :use
(:require [clojure.string :as str])
(:import (java.io.file)))
; Java access
; (.method instance & args)
; (.field instance)
; (.-field instance)
; (Class/method & args)
; (Class/field)
(java.util.Random.) ; new instance
(System/lineSeparator) ; static field
Math/PI ; static field
(java.util.Random.) ; new instance
(.nextInt (java.util.Random.) 10) ; call Method
; flow control
(if (< 3 4) ; if-clause
true
false)
; cond
(defn condexample [x]
(cond
(= x 5) (println "x is 5")
(= x 10) (println "x is 10")
:else (println "x is not defined")))
(do (println "do multiple things") ; do multiple things, return value of last expression
"help")
(loop [result [] ; loop sets recursion point, use recur to re-enter
x 5]
(if (zero? x)
result
(recur (conj result x) (dec x))))
(defn countdown [result x] ; recur works also with functions
(if (zero? x)
result
(recur (conj result x) (dec x))))
(countdown [] 5) ; [5 4 3 2 1]
;working with sequences
(first [1 2 3]) ; 1
(rest [1 2 3]) ; (2 3)
(cons 0 [1 2 3]) ; (0 1 2 3) "cons" for "construct"
(seq "Hallo") ; return sequence oof seq-able collection, e.g. (\H \a \l \l \o)
(seq? [1 2 3]) ; false
(seq? (rest [1 2 3])) ; true
(sorted-set 5 2 1 3 4) ; #{1 2 3 4 5}
(sorted-map :c 3 :b 1 :a 2) ; {:a 2, :b 1, :c 3}
(conj '(1 2 3) :a) ; (:a 1 2 3) ; for lists, add at front
(into '(1 2 3) '(:a :b :c)) ; (:c :b :a 1 2 3); for lists, add at front
(conj [1 2 3] :a) ; [1 2 3 :a] for vectors, add at end
(into [1 2 3] [:a :b :c]) ; [1 2 3 :a :b :c] for vectors, add at end
; Using the sequence library
(range 5) ; (0 1 2 3 4) end only
(range 5 10) ; (5 6 7 8 9) start + end
(range 1 10 2) ; (1 3 5 7 9) start, end, step
(repeat 5 1) ; (1 1 1 1 1)
(take 10 (iterate inc 1)) ; (1 2 3 4 5 6 7 8 9 10) iterate begins with value and continues forever, applying a function to each value to calculate the next
(take 5 (repeat 2)) ; (2 2 2 2 2) with one argument repeat returns infinite sequence
(take 6 (cycle (range 3))) ; (0 1 2 0 1 2)
(def whole-numbers (iterate inc 1))
(interleave whole-numbers ["A" "B" "C" "D"]) ; (1 "A" 2 "B" 3 "C" 4 "D") stops when one of the collections is exhausted
(apply str (interpose ", " ["apples" "bananas" "grapes"])) ; apples, bananas, grapes
; shortcut for (apply str (interpose ...))
(require '[clojure.string :refer [join]])
(join ", " ["apples" "bananas" "grapes"]) ; apples, bananas, grapes
; create collections
(list 1 2 3 4) ; (1 2 3 4)
(vector 1 2 3 4) ; [1 2 3 4]
(hash-set 1 2 3 4) ; #{1 4 3 2}
(hash-map :a 1 :b 2 :c 3) ; {:c 3, :b 2, :a 1}
(set [1 2 3 4]) ; #{1 4 3 2}
(vec (range 3)) ; [0 1 2]
; filtering
(take 10 (filter even? whole-numbers)) ; (2 4 6 8 10 12 14 16 18 20)
(def vowel? #{\a \e \i \o \u}) ; Sets act as functions that return if a value is in the set
(def consonant? (complement vowel?)) ; complement reverses the value of another function
(take-while consonant? "the-q") ; (\t \h)
(drop-while consonant? "the-q") ; (\e \- \q)
(split-at 5 (range 10)) ; [(0 1 2 3 4) (5 6 7 8 9)]
(split-with #(<= % 10) (range 0 20 2)) ; [(0 2 4 6 8 10) (12 14 16 18)]
; seq predicates
(every? odd? [1 3 5]) ; true
(some odd? [2 3 6]) ; true - returns function value of first non-null
(some #{3} (range 10)) ; 3
(not-every? even? whole-numbers) ; true
(not-any? even? whole-numbers) ; false
; transforming
(map #(format "<p>%s</p>" %) ["the" "quick" "brown"]) ; ("<p>the</p>""<p>quick</p>" "<p>brown</p>")
(reduce + (range 1 11)) ; 55 function must have two arguments. Applies to first two elements in coll, then the result with the third, ...
(sort [47 11 50]) ; (11 47 50)
(sort > [47 11 50]) ; (50 47 11), uses function for sorting
(sort-by #(.toString %) [47 11 50]) ; (11 47 50)
; list comprehension
(for [word ["the" "quick" "brown " "fox"]] ((co)mat "<p>%s</p>" word)) ;("<p>the</p>" "<p>quick</p>" "<p>brown </p>" "<p>fox</p>")
(take 10 (for [n whole-numbers :when (even? n)] n)) ; (2 4 6 8 10 12 14 16 18 20), filter with (:when)
(for [n whole-numbers :while (odd? n)] n) ; (1) :while continues only while expression is true
(for [file "ABC" rank (range 1 4)] (format "%c%d" file rank)) ; ("A1" "A2" "A3" "B1" "B2" "B3" "C1" "C2" "C3") rightmost first, then to the left
; make sure lazy seqs are evaluated
(doall [1 2 3]) ; [1 2 3]
(dorun [1 2 3]) ; nil - will not keep results in memory, good for very large colls
; Java Seq-able
(first (.getBytes "hello")) ; 104
(rest (.getBytes "hello")) ; (101 108 108 111)
(first (System/getProperties)) ; #<Entry java.runtime.name=Java(TM) SE Runtime Environment>
(first "Hello") ; \H
(apply str (reverse "Hello")) ; olleH
(re-seq #"\w+" "the quick brown fox") ; ("the" "quick" "brown" "fox") re-seq exposes an immutable seq over the matches
(seq (.listFiles (java.io.File. "."))) ; sequence of files
(count (file-seq (java.io.File. "."))) ; 92 - depth-first walk via file-seq
(require '[clojure.java.io :refer [reader]])
(take 2 (line-seq (reader "README.md"))) ; ("# test" "") note: does not close reader
(with-open [rdr (reader "README.md")] (count (line-seq rdr))) ; use with-open to close the reader
; Functions on lists
(peek '(1 2 3)) ; 1 first element
(pop '(1 2 3)) ; (2 3)
(rest '()) ; ()
(pop '()) ; IllegalStateException Can't pop empty list
; Functions on vectors
(peek [1 2 3]) ; 3 - last element
(pop [1 2 3]) ; [1 2] - removes last element
(get [\a \b \c] 2) ; \c, nil if out of range
([\a \b \c] 1) ; \b - vectors are functions, they return the value at the index
([\a \b \c] 3) ; does not work: IndexOutOfBoundsException clojure.lang.PersistentVector.arrayFor (PersistentVector.java:107)
(assoc [0 1 2 3 4] 2 :two) ; [0 1 :two 3 4] - associate a new value with an index
(subvec [0 1 2 3 4] 2) ; [2 3 4] - start
(subvec [0 1 2 3 4] 2 3) ; [2] - start and end
(concat ["a" "b" "c"] ["d" "e"]);("a" "b" "c" "d" "e") NOT A VECTOR
; Functions on maps
(keys {:name "Hans" :lastname "Test" :age 32}) ; (:age :lastname :name)
(vals {:name "Hans" :lastname "Test" :age 32}) ; (32 "Test" "Hans")
(get {:name "Hans" :lastname "Test" :age 32} :name) ; Hans - get value or null
(get {:name "Hans" :lastname "Test" :age 32} :street "unknown") ;unknown, default value
({:name "Hans" :lastname "Test" :age 32} :age) ; 32 - maps are functions of their keys
(:name {:name "Hans" :lastname "Test" :age 32}) ; Hans - keywords are also functions
(contains? {:name "Hans" :lastname "Test" :age 32 :val nil} :val) ; true, map contains val with value null
(assoc {:name "Hans"} :key "value") ; {:key "value", :name "Hans"}
(dissoc {:key "value", :name "Hans"} :name) ; {:key "value"}
(select-keys {:name "Hans" :lastname "Test" :age 32} '(:name :age)) ;{:age 32, :name "Hans"}
(merge {:name "Hans" :age 32} {:name "Jens" :lastname "Test"}) ; {:lastname "Test", :age 32, :name "Jens"} - if multiple maps contain a key, the rightmost wins
(merge-with concat {:name "Hans" :age 32} {:name "Jens" :lastname "Test"}) ; {:lastname "Test", :age 32, :name (\H \a \n \s \J \e \n \s)} - specify a function for merging
; Functions on sets
(require '[clojure.set :refer :all]) ; more functions for working with set
(def languages #{"java" "c" "d" "clojure"})
(def beverages #{"java" "chai" "pop"})
(union languages beverages) ;{ "d" "clojure" "pop" "java" "chai" "c"}
(intersection languages beverages) ; #{"java"}
(difference languages beverages) ; elements of first set minus elements of second set
(select #(.startsWith % "j") languages) ; #{"java"} - select by predicate
;Regular expressions
(re-seq #"\w+" "the quick brown fox") ; ("the" "quick" "brown" "fox") re-seq exposes an immutable seq over the matches
(re-find #"^left-" "left-eye"); #"" is regexp. If groups are used, returns vector
(re-matches #"\w+" "hallo");return match or nil
(re-pattern (str "a" "b")); creates a pattern for regexp
; work with sets and maps as database (similar to SQL)
(def testdb #{{:nr 1 :name "Jens" :lastname "Tester"} {:nr 2 :name "Karl" :lastname "Schmidt"} {:nr 3 :name "Gunther" :lastname "Eck"}})
(rename testdb {:nr :index}) ; {{:index 2, :lastname "Schmidt", :name "Karl"} {:index 1, :lastname "Tester", :name "Jens"} {:index 3,:lastname "Eck", :name "Gunther"}}
(select #(= (:lastname %) "Schmidt") testdb) ; #{{:lastname "Schmidt", :name "Karl", :nr 2}} - select by predicate
(project testdb [:name]) ; #{{:name "Karl"} {:name "Gunther"} {:name "Jens"}}
; (join relation-1 relation-2 keymap?)
; Fibonacci that consumes the stack - not a good idea
(defn stack-consuming-fibo [n]
(cond
(= n 0) 0
(= n 1) 1
:else (+ (stack-consuming-fibo (- n 1))
(stack-consuming-fibo (- n 2)))))
; Better use tail recursion
(defn tail-fibo [n]
(letfn [(fib
[current next n]
(if (zero? n)
current
(recur next (+ current next) (dec n))))]
(fib 0N 1N n)))
; even better use lazy seq
(defn lazy-seq-fibo
([]
(concat [0 1] (lazy-seq-fibo 0N 1N)))
([a b]
(let [n (+ a b)]
(lazy-seq (cons n (lazy-seq-fibo b n))))))
(take 10 (lazy-seq-fibo)) ; (0 1 1N 2N 3N 5N 8N 13N 21N 34N)
(rem (nth (lazy-seq-fibo) 1000000) 1000) ; 875N - last 3 digits of millionth fib-number
; simpler: Use iterate
(take 5 (iterate (fn [[a b]] [b (+ a b)]) [0 1])) ; ([0 1] [1 1] [1 2] [2 3] [3 5])
(defn fibo []
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1]))) ; (0 1 1 2 3 5 8 13 21 34)
; comp and partial application
(def count-if (comp count filter)) ; composed function. Applies rightmost function to argumentens, then next-rightmost to result, ...
(count-if odd? [1 2 3 4 5 6 7]) ; 4
(def add-3 (partial + 3)) ; partial application of function
(add-3 2) ; 5
; mutual recursion
(declare my-odd? my-even?) ; might require declare
(defn my-odd? [n]
(if (= n 0)
false
(my-even? (dec n))))
(defn my-even? [n]
(if (= n 0)
true
(my-odd? (dec n))))
(my-odd? 3) ; will not work well for large numbers
; try self-recursion instead
(defn parity [n]
(loop [n n par 0]
(if (= n 0)
par
(recur (dec n) (- 1 par)))))
(defn my-even2? [n]
(= 0 (parity n)))
(defn my-odd2? [n]
(= 1 (parity n)))
(my-odd2? 5) ; true
; trampoline
; if return is not a function, trampoline works like calling the function directly
(trampoline list) ; ()
(trampoline + 1 2) ; 3
; if return is a function, then trampoline assumes you want to call it recursively and calls it for you
; trampoline manages its own recur, so it will keep calling your function until it stops returning functions.
(defn trampoline-fib [n]
(let [fib (fn fib [f-2 f-1 current]
(let [f (+ f-2 f-1)]
(if (= n current)
f
#(fib f-1 f (inc current)))))] ; return anonymous function
(cond
(= n 0) 0
(= n 1) 1
:else (fib 0N 1 2))))
(trampoline trampoline-fib 9) ; 34N
; Memoization
(declare m f)
(defn m [n]
(if (zero? n)
0
(- n (f (m (dec n))))))
(defn f [n]
(if (zero? n)
1
(- n (m (f (dec n))))))
(time (m 100)) ; prints "Elapsed time: 100.2971 msecs", takes quite long
; rebind to memoized functions
(def m (memoize m))
(def f (memoize f))
(time (m 100)) ; will only need 0.0241 msecs
; however, large values will still blow the stack when starting with empty cache
(def m-seq (map m (iterate inc 0)))
(def f-seq (map f (iterate inc 0)))
(take 10 m-seq); (0 0 1 2 2 3 4 4 5 6)
(time (nth m-seq 10000)); 6180, prints "Elapsed time: 30.7671 msecs", quite fast!
(doseq [x (seq)] (println x)) ; apply to every member of sequence, return nil