LoginSignup
10
6

More than 5 years have passed since last update.

filter と remove のふたつの結果を簡単に受け取る方法

Last updated at Posted at 2015-08-18

なんとなく Clojure で書かれたコードを読みながら簡単だけど意外に知らないかもなーと思った便利そうなものを紹介してみようと思う(分かんない、たぶん皆知ってる)。

課題: 1 から 100 までの整数の列から偶数と奇数をそれぞれ集めてください

こういう課題があったときにどう書きますか?という話で、 Clojure に慣れている人ならすぐに回答出来ると思うけど慣れてない人はこんなコードを書く気がします。

(def numbers (range 1 101)) ;; (1 2 3 4 ... 97 98 99 100)

(let [even-numbers (filter even? numbers)
      odd-numbers (filter odd? numbers)]
  [even-numbers odd-numbers]) ;; [(2 4 6 8 ... 94 96 98 100) (1 3 5 7 ... 93 95 97 99)]

これの何が悪いのかと言われると何も悪くないと思う(強いて言えば冗長?)けど、ただ今回はもうちょっと Clojure のらしさを取り入れたコードを紹介したいので書き直しを試みてみます。

まず、 filter 関数が 2 回使われていますが、ここで (filter odd? x)(remove even? x) であることに気が付きます。
これを書き換えてみます。

(let [even-numbers (filter even? numbers)
      odd-numbers (remove even? numbers)]
  [even-numbers odd-numbers]) ;; [(2 4 6 8 ... 94 96 98 100) (1 3 5 7 ... 93 95 97 99)]

すると偶然にも filterremove 関数の引数が全く同一になってしまいました。こうなるとどうにかしてまとめることが出来るような気がしてきますよね。 Clojure なら簡単にできます。

(let [[even-numbers odd-numbers :as result] ((juxt filter remove) even? numbers)]
  result) ;; [(2 4 6 8 ... 94 96 98 100) (1 3 5 7 ... 93 95 97 99)]

出来ました。結果は同じですね。
ここで注目して欲しいのは juxt 関数でこいつは何をしてくれる関数かというと doc を引けば分かります。

clojure.core/juxt
([f] [f g] [f g h] [f g h & fs])
  Takes a set of functions and returns a fn that is the juxtaposition
  of those fns.  The returned fn takes a variable number of args, and
  returns a vector containing the result of applying each fn to the
  args (left-to-right).
  ((juxt a b c) x) => [(a x) (b x) (c x)]

簡単に言うと幾つかの関数を受け取って新しく関数を返し、その関数は受け取った引数をそれぞれの関数に適用した結果をベクターにして返しますよってことです。

今回の例で言えば ((juxt filter remove) even? numbers)[(filter even? numbers) (remove even? numbers)] の省略形と捉えることができます。

結果がベクターなのでそれぞれの結果を使いたい場合は上でやってるように分配束縛してしまえばいいです。

今回の例は単純な数列でしたが、特定のベクターから条件にあったものとあってないものをそれぞれ取り出したいという需要は実際のプログラムでも度々あると思うので覚えておくといい気がします。

juxt の別の便利な使い方

Clojure においてキーワードは関数と同様に扱えます。 (:name {:name "ayato_p" :age 24}) のように使いますよね。
{:name "ayato_p" :age 24} みたいなマップから :name:age を両方取り出したいというときどうしますか?
勿論、分配束縛も使えますが分配束縛を使いたいシーンは :name:age の値をそれぞれ独立して使いたいシーンです。そうじゃないとき、純粋に値のベクターを得たいというシーンがあったとしたら、次のように書けます。

((juxt :name :age) {:name "ayato_p" :age 24 :weight :secret :height :secret}) ;; ["ayato_p" 24]

まとめ

juxt 便利なので使いましょう(乱用注意)。

10
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6