はじめに
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 ↩