イディオム集¶
複数のコレクションの要素を index ごとにまとめる¶
[0 1 2 3 4]
[:a :b :c :d :e]
というふたつのベクタがあるときにこれらの要素を簡単に index ごとにまとめる方法。
(let [a [0 1 2 3 4]
b [:a :b :c :d :e]]
(map vector
a
b))
;; ([0 :a] [1 :b] [2 :c] [3 :d] [4 :e])
気を付けないといけないのは map
が一番短かいコレクションの長さで止まるので、全ての要素に対して行ないたい場合などは注意が必要です。
マップを平坦なシーケンスへと変換する¶
次のようなマップデータを平坦なシーケンスへと変更したいときに使える方法です。
{:name "ayato-p"
:age "24"
:address "Japan"}
これはこのまま seq
などを使うと平坦なシーケンスにはなりません。
(seq {:name "ayato-p"
:age "24"
:address "Japan"})
;; ([:name "ayato-p"] [:age "24"] [:address "Japan"])
この場合は次のように書きます。
(apply concat {:name "ayato-p"
:age "24"
:address "Japan"})
;; (:name "ayato-p" :age "24" :address "Japan")
seq
+ flatten
という方法もありますが、 flatten
がネストした配列も全て平滑化してしまうため、この場合はあまり使うことが出来ません。
可変長引数を受け取る関数にシーケンスのデータを渡したい¶
(def v ["foo" "bar" "baz"])
(defn f [& args]
(clojure.string/join ", " args))
このような関数とデータがあった場合にどのようにすれば関数 f
へベクタ
v
が渡せるかという問題です。 これは次のように書けます。
(apply f v)
;; "foo, bar, baz"
apply
を使う関数適用の形はよく使うので覚えておくと良いでしょう。
また、ベクタではなくマップの場合は次のように書けます。
(def m {:name "ayato-p" :age 24})
(defn g [& {:as m :keys [name age]}]
(str "name: " name ", "
"age: " age))
(apply g
(apply concat m))
;; "name: ayato-p, age: 24"
シーケンスの全要素に関数を適用して nil
を捨てる¶
keep
関数を使いましょう。
(def people [{:name "ayato_p" :age 11}
{:name "alea12" :age 10}
{:name "zer0_u"}])
(remove nil? (map :age people)) ;(11 10)
(keep :age people) ;(11 10)
ある値が boolean かどうかを知りたい¶
(defn boolean? [b]
(or (true? b)
(false? b)))
(boolean? true) ;true
(boolean? false) ;true
(boolean? Boolean/TRUE) ;true
(boolean? Boolean/FALSE) ;true
(boolean? (Boolean. "true")) ;false
(boolean? (Boolean. "false")) ;false
(boolean? "") ;false
(boolean? nil) ;false
(boolean? 0) ;false
(boolean? 1) ;false
これでほとんどの場合は事足りるでしょう。
複数の候補の中から nil
でない値を見つけたら値を返す¶
or
が使えます。
(or nil
"ayato-p")
;; "ayato-p"
ただ、 false
を見つけても無視されるので、 false
が欲しい場合は気をつけましょう。
シーケンスが空かどうかを確かめたい¶
seq
関数を使います。
(def ev [])
(def v [1 2])
(if (seq nil)
"not nil"
"nil") ;"nil"
(if (seq ev)
"not empty"
"empty") ;"empty"
(if (seq v)
"not empty"
"empty") ;"not empty"
seq
関数は便利なので、 nil
に対しても使えるので “nil
または空のシーケンスか” というテストが簡単に出来ます。
マップに対して条件を満すときだけ assoc/dissoc
して、それ以外のときはそのまま返したい¶
cond->
を使うと簡単です。
(def m {:foo 1 :bar 2})
(cond-> m
true (assoc :baz 3)) ;{:foo 1, :bar 2, :baz 3}
(cond-> m
false (assoc :baz 3)) ;{:foo 1, :bar 2}
reduce
を途中で止めたい¶
reduced
を使いましょう。
まず次のような無限の数値シーケンスに対してかけ算することを考えます。このときかけ算なので
0
を見つけたところで 0
を返すことが可能です(もし 0
が見つからなくて本当に無限のシーケンスがきたら止まらないですね)。
(reduce (fn [acc x]
(if (zero? x)
(reduced 0)
(* acc x)))
1
(cycle [9 8 7 6 5 4 3 2 1 0]))
マップのキー(バリュー)すべてに対して関数を適用( map )したい¶
マップデータのキー(バリュー)すべてに対して関数を適用したいというのはよくある問題です。 例えば次のコードを考えてみます。
(def m {"key1" 1
"key2" 2
"key3" 3})
このときキーをキーワード化したいと思うことがあるかもしれません。そのようなときは次のように書けます。
(reduce-kv (fn [m k v]
(assoc m (keyword k) v))
{}
m)
;; {:key1 1, :key2 2, :key3 3}
また、これを一般化した関数が
plumbing
というライブラリにある( map-keys
, map-vals
)のでこちらを使うことを検討してもいいかもしれません。
ベクターからインデックスを元に要素を落としたい¶
Clojure には沢山の関数があるのでそのような関数がありそうなものですが、残念ながらありません。次のように書きます。
(defn drop-by-idx [v idx]
(vec (concat (subvec v 0 idx)
(subvec v (inc idx)))))
(drop-by-idx [0 1 2 3 4 5 6 7 8 9]
5)
;; [0 1 2 3 4 6 7 8 9]
java.util.LinkedList のインスタンスをベクターにしたい¶
(let [linkedlist (doto (java.util.LinkedList.)
(.add "foo")
(.add "bar")
(.add "baz"))]
;; (nth linkedlist 1) ;=> Unable to resolve symbol: linkedlist in this context
(nth (into [] linkedlist) 1))
;; "bar"
ループの間で何度か更新する値を保持していたい¶
何らかの繰り返し処理中に更新や参照をしたい値を一時的に保持しておきたいというのはよくありますが、この場合
map
や reduce
を使うことはできません。 なので loop
を使います。
;; この例であれば reduce や apply,+ の方がいいですが…
(loop [li (range 10)
total 0]
(if-let [a (first li)]
(recur (rest li) (+ a total))
total))
プログラム全体で参照できるような簡易データベースが欲しい¶
レキシカルスコープを使って java.util.Map
のインスタンスを束縛してしまうという方法があります。
(あまり推奨するわけではないですが、こういう方法があるというくらいで知っておいても良いかもしれません)
(let [^java.util.Map +easy-database+ (java.util.Collections/synchronizedMap (java.util.WeakHashMap.))]
(defn set-data [key val]
(.put +easy-database+ key val))
(defn get-data [key]
(.get +easy-database+ key)))
(set-data :foo 1)
(get-data :foo)
;; 1
falsy な値をリストから除去する¶
filter
+ identity
で実現できます。
(filter identity [nil false true 1 "hello" [1 2] {:foo 1} :hoge])
;; (true 1 "hello" [1 2] {:foo 1} :hoge)
オブジェクトの一覧にインデックスを付ける¶
map-indexed
+ vector
の組み合わせで実現できます。
(map-indexed vector (repeat 5 {}))
;; ([0 {}] [1 {}] [2 {}] [3 {}] [4 {}])
こうすることで次のように利用できます。
(for [[idx m] (map-indexed vector (repeat 5 {}))]
(str idx " is " (pr-str m)))
;; ("0 is {}" "1 is {}" "2 is {}" "3 is {}" "4 is {}")
または次のようにも書けます。
(group-by first (map-indexed vector (repeat 5 {})))
;; {0 [[0 {}]], 1 [[1 {}]], 2 [[2 {}]], 3 [[3 {}]], 4 [[4 {}]]}
(into {} (map-indexed vector (repeat 5 {})))
;; {0 {}, 1 {}, 2 {}, 3 {}, 4 {}}
シーケンスから最初に条件に合致するものを取得する¶
幾つか実装方法がありますが、ここではひとつ例を示します。
(defn find-first [pred coll]
(first (drop-while (complement pred) coll)))
述語関数とコレクションをひとつ引数に受け取る関数として実装します。
drop-while
によって条件に合致しないものを全て drop
し、残ったものの先頭を返却するというものです。
(defn prime? [x]
(reduce (fn [res n]
(if (zero? (mod x n))
(reduced false)
true))
true
(range 2 (inc (Math/round (Math/sqrt x))))))
(find-first prime?
(range 1000 Double/POSITIVE_INFINITY)) ;; 1009
同様の関数が medley というライブラリでも実装されています。 ただ、 Clojure の場合遅延シーケンスがあるので条件に一致しない場合、処理が終了しない可能性があることに注意してください。