はじめに
4ever-clojureの練習問題の回答集。全ての問題の、まだ半分も解いていない。
回答を通して、Clojureだとシンプルなコードが書けるということが良く分かった。
一方でシーケンス操作関数をゴリゴリ使っている自作関数で、シーケンス操作関数をゴリゴリ使っている自作関数を更に呼び出す、なんてことをやっていると実行速度が目に見えて遅くなる。Clojureはまだ人類には早い言語のように思われる1。
チートシート
- 結合:
cons
,conj
,concat
- 抜き取り:
first
,second
,ffirst
,nth
,rest
,butlast
,nthrest
,last
- マッピング関数:
map
,mapcat
,map-indexed
,zipmap
,interleave
,interpose
- フィルタ:
filter
,keep
,remove
- 数列生成:
range
,repeat
,iterate
,cycle
- 関数適用:
reduce
,reduce-kv
,reductions
,apply
- 再帰:
loop
,recur
,for
- 条件分岐:
if
,cond
,condp
,case
- シーケンス変形:
flatten
,group-by
,distinct
- 正規表現:
re-
- 述語:
pos?
,neg?
,zero?
,some
- シーケンス情報:
frequencies
- 関数生成:
fn
,comp
,complement
,juxt
- スレッドマクロ:
->
,->>
- マップ関連:
keys
,key
,vals
,val
(kv / entry) - その他:
identity
(non) | (short) | sorted- |
|
---|---|---|---|
list |
& items |
(non) | (non) |
vector |
a b c d e f & args |
(vec coll) |
(non) |
hash-set |
& keys |
(set coll) |
(sorted-set & keys) |
hash-map |
& keyvals |
(non) | (sorted-set & keyvals) |
sequence |
coll |
(seq coll) |
(non) |
(collection/coll
関数はない)
(non) | -last |
-nth |
-while |
|
---|---|---|---|---|
take |
n coll |
n coll |
n coll (step) |
pred coll |
drop |
n coll |
n coll |
(non) | pred coll |
rand |
n |
(non) | coll |
(non) |
(non) | -at |
-with |
|
---|---|---|---|
split |
s re |
n coll |
pred coll |
merge |
& maps |
(non) | f maps |
(non) | -by |
-all |
|
---|---|---|---|
sort |
comp coll |
keyfn comp coll |
(non) |
partition |
n step coll |
f coll |
n step coll |
回答
1-4
;; 1: Nothing but the Truth
(= __ true) ; true
;; 2: Simple Math
(= (- 10 (* 2 3)) __) ; 4
;; 3: Strings
(= __ (.toUpperCase "hello world")) ; "HELLO WORLD"
;; 4: Lists
(= (list __) '(:a :b :c)) ; :a :b :c
各種関数の基本操作。
5: conj on lists
(= __ (conj '(2 3 4) 1))
(= __ (conj '(3 4) 2 1))
;; '(1 2 3 4)
cons
は引数形式が(cons x seq)
で、x
は常に先頭に付く。
conj
は引数形式が(conj coll element & elements
で、①cons
とは引数の順番が逆で、②だからこそ複数の引数が取れる。③ (伝統的なデータ構造である) リストに対してはcons
と同じように先頭に要素を追加し、ベクタ (と遅延シーケンス) に対しては末尾に追加をする。セットには順番がないので追加位置を考える必要はなく、マップにconj
を適用することはできない。
※coll
= コレクション = リスト、ベクタ (配列)、セット (集合)、マップ (辞書)
6: Vectors
(= [__] (list :a :b :c) (vec '(:a :b :c)) (vector :a :b :c))
;; :a :b :c
ベクタを作成する際には、[]
で書くのが一般的。vec/vector
の違いは引数の取り方だけで、(apply vector '(:a :b :c))
と(vec '(:a :b :c))
が等価。(= [:a :b :c] (list :a :b :c))
のように、ベクタとリストが=
である点が面白い。
7: conj on vectors
(= __ (conj [1 2 3] 4))
(= __ (conj [1 2] 3 4))
;; [1 2 3 4]
ベクタでconj
に複数の引数を渡した際、その引数は前から順番に追加される。
8: Sets
(= __ (set '(:a :a :b :c :c :c :c :d :d)))
(= __ (clojure.set/union #{:a :b :c} #{:b :c :d}))
;; #{:a :b :c :d}
セットはPythonの集合とほぼ同じ。union
(和集合)以外にも、intersection
(積集合)、difference
(差集合)、select
がある。
9: conj on sets
(= #{1 2 3 4} (conj #{1 4 3} __))
;; 2
セットはダブった要素を捨てるので、2 2 3 4 4 1
等も正解。
10: Maps
(= __ ((hash-map :a 10, :b 20, :c 30) :b))
(= __ (:b {:a 10, :b 20, :c 30}))
;; 20
hash-map
と聞くと怖く見えるが、要はPython
の辞書型。このマップは自身が関数のように振る舞い、引数をキーとした値を返す。また逆に、キーを関数のように振る舞わせることもできる。
なおカンマ,
は空白扱いで、あってもなくても良い。
11: conj on maps
(= {:a 1, :b 2, :c 3} (conj {:a 1} __ [:c 3]))
;; [:b 2]
conj
でマップにベクタを追加すると、そのベクタはキー・値のペアとして扱われる。
(conj {:a 1} {:b 2 :c 3})
も同様の結果になるが、(conj {:a 1} [:b 2 :c 3])
はエラー。
12: Sequences
(= __ (first '(3 2 1)))
(= __ (second [2 3 4]))
(= __ (last (list 1 2 3)))
;; 3
Common Lispと異なり、third
以降は存在しない点に注意。(first (first x))
に等価なffirst
という関数が存在する。
13: rest
(= __ (rest [10 20 30 40]))
;; [20 30 40]
14: Functions
(= __ ((fn add-five [x] (+ x 5)) 3))
(= __ ((fn [x] (+ x 5)) 3))
(= __ (#(+ % 5) 3))
(= __ ((partial + 5) 3))
;; 8
Common Lispの#'(lambda (x) (+ x 5)
や、Pythonのlambda x: x + 5
に当たる操作。
#()
の形式は%
が引数を表す。2つ以上引数を取る際は、%1
、%2
と番号を振らないといけない。この形式だと、①可変長の引数を取ることはできず、②コレクションを略記で返せないという制限がある。例えば((fn [x] [x x]) 3)
は[3 3]
が返るが、(#([% %]) 3)
はエラー。(#(vector % %) 3)
ならOK。(#(vec '(% %)) 3)
は評価の問題で期待した結果は返らないが、(#(vec [% %]) 3)
はOK。
15: Double Down
(= (__ 2) 4)
(= (__ 3) 6)
(= (__ 11) 22)
(= (__ 7) 14)
;; #(* 2 %)
16: Hello World
(= (__ "Dave") "Hello, Dave!")
(= (__ "Jenn") "Hello, Jenn!")
(= (__ "Rhea") "Hello, Rhea!")
;; #(str "Hello, " % "!")
文字列の結合にはstr
が使える。
17: map
(= __ (map #(+ % 5) '(1 2 3)))
;; [6 7 8]
問題6であったように、'(6 7 8)
とリストを利用してもOK。
なお、(type (map #(+ % 5) '(1 2 3)))
を実行すると、clojure.lang.LazySeq
が返る。map
等の組み込み関数は頻繁にこのLazySeq
を返してきて、この場合はリストやベクタといった構造をあまり気にすることなく、組み込み関数を適用できる。
18: filter
(= __ (filter #(> % 5) '(3 4 5 6 7)))
;; [6 7]
引数のfn
がtrue
になるような値が残る。
反対の操作を行うremove
、true/false
の結果を残すkeep
。その他にもtake-while
、drop-while
、split-with
といったフィルタ一族が存在する (take-
、drop-
、split-
はそれぞれ自身の一族も持っている)。
(remove even? [0 1 2 3 4]) ; (1 3)
(keep even? [0 1 2 3 4]) ; (true false true false true)
(take-while even? [0 1 2 3 4]) ; (0)
(drop-while even? [0 1 2 3 4]) ; (1 2 3 4)
(split-with even? [0 1 2 3 4]) ; [(0) (1 2 3 4)]
フィルター条件には、セットも使える。
(filter #{1 2 10} [1 3 4 2 5 6]) ; (1 2)
19: Last Element
(= (__ [1 2 3 4 5]) 5)
(= (__ '(5 4 3)) 3)
(= (__ ["b" "c" "d"]) "d")
;; last
;; (comp first reverse)
(comp first reverse)
は#(first (reverse %))
と等価。関数が適用される順番は、後ろから前。単に関数をいくつか適用するだけの場合、comp
で記述を簡潔にできる。
#(-> % reverse first)
/#(->> % reverse first)
としても良い。->/->>
は関数を前から後ろの順繰りで適用させるマクロで、->
は返り値が次の関数の最初の引数に、->>
は返り値が次の関数の最後の引数になる。
20: Penultimate Element
(= (__ (list 1 2 3 4 5)) 4)
(= (__ ["a" "b" "c"]) "b")
(= (__ [[1 2] [3 4]]) [1 2])
;; (comp second reverse)
21: Nth Element
(= (__ '(4 5 6 7) 2) 6)
(= (__ [:a :b :c] 0) :a)
(= (__ [1 2 3 4] 1) 2)
(= (__ '([1 2] [3 4] [5 6]) 2) [5 6])
;; #(nth %1 %2)
;; #((zipmap (range) %1) %2)
zipmap
は、(zipmap [0 1 2] [10 11 12]) ;=> {0 10, 1 11, 2 12}
のようにしてマップを生成してくれる。これにはinterleave
という親戚もいて、(interleave [0 1 2] [10 11 12]) ;=> (0 10 1 11 2 12)
のように遅延シーケンスを返す。(interpose "," ["a" "b" "c"]) ;=> ("a" "," "b" "," "c")
や(clojure.string/join "," ["a" "b" "c"]) ;=> "a,b,c"
もある。
range
はPythonのように(range 0 4 2) ;=> (0 2)
として使える一方、引数なしで呼び出すと0から無限までの遅延シーケンスを返す。repeat
はある値の有限/無限の遅延シーケンスを返し、iterate
はある関数適用の無限シーケンスを返し、cycle
はあるコレクションを繰り返す無限シーケンスを返す。zipmap等に無限シーケンスを与えたとき、有限のシーケンスが混ざっていれば、その有限の長さに合わせて処理は止まる。
22: Count a Sequence
(= (__ '(1 2 3 3 1)) 5)
(= (__ "Hello World") 11)
(= (__ [[1 2] [3 4] [5 6]]) 3)
(= (__ '(13)) 1)
(= (__ '(:a :b :c)) 3)
;; count
;; #(inc (last (interleave % (range))))
23: Reverse a Sequence
(= (__ [1 2 3 4 5]) [5 4 3 2 1])
(= (__ (sorted-set 5 7 2 7)) '(7 5 2))
(= (__ [[1 2][3 4][5 6]]) [[5 6][3 4][1 2]])
;; reverse
;; #(map val (sort-by key > (zipmap (range) %)))
;; #(reduce conj '() %)
sort-by
はその名の通り、引数の関数に応じて並び替えを行う。sort
という関数もあり、(sort #(compare (last %1) (last %2)) {:b 1 :c 3 :a 2})
と(sort-by last {:b 1 :c 3 :a 2})
が等価。マップやセットはシーケンスではないので要素の順番は考慮されないが、sorted-map/set
とsorted-map/set-by
で順番を固定することができる。
マップについては、key
やval
でキーや値を取得することができる。マップはキーと値が組み合わさって初めて1つの値になるので、(key {:a 1})
ではなく、(key (first {:a 1}))
としなければいけない。
#(reduce conj '() %)
は他の人の解答例にあったもので、「リストに対するconj
は先頭に値が追加される」ことを利用している。美しい。
24: Sum It All Up
(= (__ [1 2 3]) 6)
(= (__ (list 0 -2 5 5)) 8)
(= (__ #{4 2 1}) 7)
(= (__ '(0 0 -1)) -1)
(= (__ '(1 10 3)) 14)
;; apply +
+
関数は(+ 1 2 3) ;=> 6
のように可変長の引数を取る。(+ [1 2 3])
のように書くとエラーになってしまうので、引数の括弧を取り払うために(apply + [1 2 3]) ;=> 6
と書く必要がある。
またreduce
について、今回の例だと(reduce + [1 2 3]) ;=> 6
はapply
と同じ結果を返す。しかし内部的には(+ (+ 1 2) 3)
のように、2つの引数を取る関数を繰り返し適用している。ある回での返り値は次の回での1番目の引数となり、ここに初期値を指定することもできる (問30で初期値を利用している)。reduce
の結果をステップごとに保存するreductions
というズルいやつもいる。
25: Find the odd numbers
(= (__ #{1 2 3 4 5}) '(1 3 5))
(= (__ [4 2 1 6]) '(1))
(= (__ [2 2 4 6]) '())
(= (__ [1 1 1 3]) '(1 1 1 3))
;; filter odd?
26: Fibonacci Sequence
(= (__ 3) '(1 1 2))
(= (__ 6) '(1 1 2 3 5 8))
(= (__ 8) '(1 1 2 3 5 8 13 21))
;; #(reverse (nth (iterate (fn [fib] (conj fib (+ (first fib) (second fib)))) '(1 1)) (- % 2)))
Common Lispとは異なり、nth
のインデックス指定は引数の最後で行う。
27: Palindrome Detector
(false? (__ '(1 2 3 4 5)))
(true? (__ "racecar"))
(true? (__ [:foo :bar :foo]))
(true? (__ '(1 1 3 3 1 1)))
(false? (__ '(:a :b :c)))
;; #(= (seq %) (reverse %))
seq
は文字列を、文字のシーケンスに変換することができる。
28: Flatten a Sequence
(= (__ '((1 2) 3 [4 [5 6]])) '(1 2 3 4 5 6))
(= (__ ["a" ["b"] "c"]) '("a" "b" "c"))
(= (__ '((((:a))))) '(:a))
;; flatten
;; #(loop [x % flat []]
;; (if (empty? x)
;; flat
;; (let [fx (first x)]
;; (if (coll? fx)
;; (recur (concat [(first fx)] (rest fx) (rest x)) flat)
;; (recur (concat (rest x)) (conj flat fx))))))
;; #(filter (complement sequential?) (tree-seq sequential? seq %))
Clojure唯一の再帰構造loop/recur
を利用。どんなシーケンスに対しても順番を保持した上で結合させる手段をconcat
しか知らないが、他にも何かあるかもしれない。
他の人の解答例を見るとloop/recur
を使わない再帰が良く見られるが、これはClojureでは良くないスタイルのはず。tree-seq
を使った解法は、他の人の解答例にあったもの。complement
は主に述語関数を引数に取って、その述語本来の返り値の逆を返すような関数を作る。sequential?
はシーケンスに対して (長さがゼロでも) true
を返す述語。tree-seq
は深さ優先探索を行い、ノードが引数述語の条件を満たしていれば、それに続く引数関数を適用する。
(tree-seq sequential? seq '((1 2 (3)) (4)))
;; (((1 2 (3)) (4)) (1 2 (3)) 1 2 (3) 3 (4) 4)
;; clojuredocs/tree-seq 記載の説明
;;=> (((1 2 (3)) (4))
;; (1 2 (3))
;; 1
;; 2
;; (3)
;; 3
;; (4)
;; 4
ちなみにCommon Lispだと、flatten
は下記のようになる2。
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
29: Get the Caps
(= (__ "HeLlO, WoRlD!") "HLOWRD")
(empty? (__ "nothing"))
(= (__ "$#A(*&987Zf") "AZ")
;; #(apply str (filter (fn [c] (Character/isUpperCase c)) %))
;; 手元のClojure環境だと上のコードでパスできるが、4everのサイトだと通らない。
Javaの関数呼び出しを知っているかどうか。『プログラミングClojure』によれば、Clojureでは多くの文字列関数がJava呼び出しに依存している。
30: Compress a Sequence
(= (apply str (__ "Leeeeeerrroyyy")) "Leroy")
(= (__ [1 1 2 3 3 2 2 3]) '(1 2 3 2 3))
(= (__ [[1 2] [1 2] [3 4] [1 2]]) '([1 2] [3 4] [1 2]))
;; reduce (fn [x y] (if (nil? x) [y] (if (= (last x) y) x (conj x y)))) nil
;; #(map first (partition-by identity %))
2つ目の解答は、他の人の答案を参考にしたもの。
partition-by
は (true/false
ではなく) 引数関数の返り値が切り替わるタイミングで、区切ってくれる。インデックスで区切るpartition/partition-all
もある。
(partition-by identity ...)
はイディオムとして覚えても良い気がする。内部的には、partition-by
は引数関数の返り値を前から連続して確認し、一致するところまでをtake-while
で切り取るという挙動をしている。
take
親族は有限・無限のシーケンスから値を取っていくもので、take/take-while/take-nth/take-last
とあり、どれもそこそこ利用される。例えばtake-nth
は、N個飛びで値が欲しいときに利用される。
31: Pack a Sequence
(= (__ [1 1 2 1 1 1 3 3]) '((1 1) (2) (1 1 1) (3 3)))
(= (__ [:a :a :b :b :c]) '((:a :a) (:b :b) (:c)))
(= (__ [[1 2] [1 2] [3 4]]) '(([1 2] [1 2]) ([3 4])))
;; #(partition-by identity %)
32: Duplicate a Sequence
(= (__ [1 2 3]) '(1 1 2 2 3 3))
(= (__ [:a :a :b :b]) '(:a :a :a :a :b :b :b :b))
(= (__ [[1 2] [3 4]]) '([1 2] [1 2] [3 4] [3 4]))
(= (__ [44 33]) [44 44 33 33])
;; #(mapcat (fn [x] [x x]) %)
map
を利用していると[[1 2] [3 4] [5 6]]
のような結果が返ることが多々あり、これらをflatten
あるいはconcat
して利用するということは良くある。mapcat
は、map
の結果にconcat
を適用したものを返してくれる。concat
はCommon Lispのappend
に当たるもの。
33: Replicate a Sequence
(= (__ [1 2 3] 2) '(1 1 2 2 3 3))
(= (__ [:a :b] 4) '(:a :a :a :a :b :b :b :b))
(= (__ [4 5 6] 1) '(4 5 6))
(= (__ [[1 2] [3 4]] 2) '([1 2] [1 2] [3 4] [3 4]))
(= (__ [44 33] 2) [44 44 33 33])
;; #(mapcat (fn [x] (repeat %2 x)) %1)
34: Implement range
(= (__ 1 4) '(1 2 3))
(= (__ -2 2) '(-2 -1 0 1))
(= (__ 5 8) '(5 6 7))
;; range
;; #(take (- %2 %1) (iterate inc %1))
35: Local bindings
(= __ (let [x 5] (+ 2 x)))
(= __ (let [x 3, y 10] (- y x)))
(= __ (let [x 21] (let [y 3] (/ x y))))
;; 7
36: Let it Be
(= 10 (let __ (+ x y)))
(= 4 (let __ (+ y z)))
(= 1 (let __ z))
;; [z 1 y 3 x 7]
37: Regular Expressions
(= __ (apply str (re-seq #"[A-Z]+" "bA1B3Ce ")))
;; "ABC"
正規表現を行うre-
一族のお話。(re-seq #"[A-Z]+" "bA1B3Ce ")
の部分では、("A" "B" "C")
が返っている。
38: Maximum value
(= (__ 1 8 3 4) 8)
(= (__ 30 20) 30)
(= (__ 45 67 11) 67)
;; max
;; (fn [& x] (reduce #(if (> %1 %2) %1 %2) x))
reduce
を利用するために、&
を利用して引数をまとめてしまっているのがミソ。
39: Interleave Two Seqs
(= (__ [1 2 3] [:a :b :c]) '(1 :a 2 :b 3 :c))
(= (__ [1 2] [3 4 5 6]) '(1 3 2 4))
(= (__ [1 2 3 4] [5]) [1 5])
(= (__ [30 20] [25 15]) [30 25 20 15])
;; interleave
;; mapcat (fn [a b] [a b])
40: Interpose a Seq
(= (__ 0 [1 2 3]) [1 0 2 0 3])
(= (apply str (__ ", " ["one" "two" "three"])) "one, two, three")
(= (__ :z [:a :b :c :d]) [:a :z :b :z :c :z :d])
;; interpose
;; #(butlast (mapcat (fn [x] [x %1]) %2))
butlast
は、シーケンスの最後の要素を落としてくれる。(drop-last 1 coll)
と同じ。
drop
一族はdrop-last
と、既出のdrop-while
、drop-while
のインデックス版であるdrop
の3人くらい。
41: Drop Every Nth Item
(= (__ [1 2 3 4 5 6 7 8] 3) [1 2 4 5 7 8])
(= (__ [:a :b :c :d :e :f] 2) [:a :c :e])
(= (__ [1 2 3 4 5 6] 4) [1 2 3 5 6])
;; #(mapcat (fn [x] (take (dec %2) x)) (partition-all %2 %1))
42: Factorial Fun
(= (__ 1) 1)
(= (__ 3) 6)
(= (__ 5) 120)
(= (__ 8) 40320)
;; #(apply * (range 1 (inc %)))
43: Reverse Interleave
(= (__ [1 2 3 4 5 6] 2) '((1 3 5) (2 4 6)))
(= (__ (range 9) 3) '((0 3 6) (1 4 7) (2 5 8)))
(= (__ (range 10) 5) '((0 5) (1 6) (2 7) (3 8) (4 9)))
;; #(for [i (range %2)] (take-nth %2 (nthrest %1 i)))
便利すぎるリスト内包表記for
の登場。形は違えど、使い勝手はPythonのものとそれほど変わらない。
nthrest
は、引数番目以降のシーケンスを返してくれる。
44: Rotate Sequence
(= (__ 2 [1 2 3 4 5]) '(3 4 5 1 2))
(= (__ -2 [1 2 3 4 5]) '(4 5 1 2 3))
(= (__ 6 [1 2 3 4 5]) '(2 3 4 5 1))
(= (__ 1 '(:a :b :c)) '(:b :c :a))
(= (__ -4 '(:a :b :c)) '(:c :a :b))
;; #(let [c (count %2) n (if (> %1 0) (rem %1 c) (+ (rem %1 c) c))]
;; (apply concat (reverse (split-at n %2))))
Clojureのインデックス指定の引数は、負値を指定してもゼロ扱いされるのが不便ではある。
45: Intro to Iterate
(= __ (take 5 (iterate #(+ 3 %) 1)))
;; [1 4 7 10 13]
初期値の1も結果に入ってくるのが、少し印象的。
46: Flipping out
(= 3 ((__ nth) 2 [1 2 3 4 5]))
(= true ((__ >) 7 8))
(= 4 ((__ quot) 2 8))
(= [1 2 3] ((__ take) [1 2 3 4 5] 3))
;; #(fn [x y] (% y x))
関数を引数に取り、その関数が取る引数の順序を逆にした関数を返す、高階関数。既出のcomplement
と雰囲気は似ている。
47: Contain Yourself
(contains? #{4 5 6} __)
(contains? [1 1 1 1 1] __)
(contains? {4 :a 2 :b} __)
(not (contains? [1 2 4] __))
;; 4
ベクタに対するcontains?
は、ベクタがその値を含んでいるかどうかではなく、引数インデックスに値があるかどうかを返す。つまり、(contains? vec n)
は(> (count vec) n)
と等価。ベクタがある値を含んでいるかどうかを知りたいときは、some
を使う。
48: Intro to some
(= __ (some #{2 7 6} [5 6 7 8]))
(= __ (some #(when (even? %) %) [5 6 7 8]))
;; 6
some
には?
が付いておらず、返り値もtrue/false
ではなく、最初にヒットした値。
49: Split a sequence
(= (__ 3 [1 2 3 4 5 6]) [[1 2 3] [4 5 6]])
(= (__ 1 [:a :b :c :d]) [[:a] [:b :c :d]])
(= (__ 2 [[1 2] [3 4] [5 6]]) [[[1 2] [3 4]] [[5 6]]])
;; split-at
;; #(vector (take %1 %2) (nthrest %2 %1))
50: Split by Type
(= (set (__ [1 :a 2 :b 3 :c])) #{[1 2 3] [:a :b :c]})
(= (set (__ [:a "foo" "bar" :b])) #{[:a :b] ["foo" "bar"]})
(= (set (__ [[1 2] :a [3 4] 5 6 :b])) #{[[1 2] [3 4]] [:a :b] [5 6]})
;; #(vals (reduce (fn [x y] (merge-with concat x {(type y) [y]})) {} %))
;; #(vals (group-by type %))
2つめの解答は、他人の解答例を参照。
51: Advanced Destructuring
(= [1 2 [3 4 5] [1 2 3 4 5]] (let [[a b & c :as d] __] [a b c d]))
;; [1 2 3 4 5]
let
の分配束縛。
52: Intro to Destructuring
(= [2 4] (let [[a b c d e f g] (range)] __))
;; [c e]
53: Longest Increasing Sub-Seq
(= (__ [1 0 1 2 3 0 4 5]) [0 1 2 3])
(= (__ [5 6 1 3 2 7]) [5 6])
(= (__ [2 3 3 4 5]) [3 4 5])
(= (__ [7 6 5 4]) [])
;; #(let [r (reductions (fn [x y] (if (= y (inc (last x))) (conj x y) [y])) [-1] %)
;; m (apply max (conj (map count r) 2))]
;; (replace {nil []} (first (filter (fn [x] (= (count x) m)) r))))
美しい解答ではないが、この問題については他の人の解答例もこんなもんだった。
54: Partition a Sequence
(= (__ 3 (range 9)) '((0 1 2) (3 4 5) (6 7 8)))
(= (__ 2 (range 8)) '((0 1) (2 3) (4 5) (6 7)))
(= (__ 3 (range 8)) '((0 1 2) (3 4 5)))
;; #(filter (fn [x] (= (count x) %1))
;; (for [n (range (quot (count %2) %1))] (take %1 (nthrest %2 (* n %1)))))
この解答も少し長いが、再帰よりもシーケンス関数を利用するのがClojureらしさだと思っているので、これで。
55: Count Occurences
(= (__ [1 1 2 3 2 1 1]) {1 4, 2 2, 3 1})
(= (__ [:b :a :b :a :b]) {:a 2, :b 3})
(= (__ [:b :a :b :a :b]) {:a 2, :b 3})
;; frequencies
;; #(apply merge (map (fn [e] {(key e) (count (val e))}) (group-by identity %)))
frequencies
は名前の通り、出現回数を返す。
56: Find Distinct Items
(= (__ [1 2 1 3 1 2 4]) [1 2 3 4])
(= (__ [:a :a :b :b :c :c]) [:a :b :c])
(= (__ '([2 4] [1 2] [1 3] [1 3])) '([2 4] [1 2] [1 3]))
(= (__ (range 50)) (range 50))
;; distinct
distinct
(はっきりとした、識別された) はダブリを除いたシーケンスを返す。
-
『こういうプログラムが書きたいんだ、というプログラムを 書いてみることだ。無制限の資源があると思って書いてみよう。 100年後でなく現在でも、無制限の資源を想像することはできるはずだ。』, 百年の言語, ポール・グレアム (川合史朗 訳), http://practical-scheme.net/trans/hundred-j.html ↩
-
ユーティリティ関数, On Lisp, ポール・グレアム(野田開 訳), https://www.asahi-net.or.jp/~kc7k-nd/onlispjhtml/utilityFunctions.html ↩