イディオム集

複数のコレクションの要素を 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

これでほとんどの場合は事足りるでしょう。

ref: Special Forms

複数の候補の中から 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"

ループの間で何度か更新する値を保持していたい

何らかの繰り返し処理中に更新や参照をしたい値を一時的に保持しておきたいというのはよくありますが、この場合 mapreduce を使うことはできません。 なので 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 の場合遅延シーケンスがあるので条件に一致しない場合、処理が終了しない可能性があることに注意してください。