ずらしてみよう

ずいぶんとご無沙汰様になってしまいましたが、まだまだ遊んでますよー。Haskellで…


Haskellで遊ぶよさんの takeWhile2とdropWhile2 を読んでいてちょっと前にどこかのプレゼン資料で見たちょっと感心したテクニックを思い出しました…

takeWhile のシグネチャ
takeWhile :: (a -> Bool) -> [a] -> [a]

で、takeWhile2はしたがって
takeWhile2 :: (a -> a -> Bool) -> [a] -> [a]
というようにパラメタを二つとるtakeWhileというわけなんですが、この「テクニック」を使うとtakeWhileを使ってtakeWhile2のようなことをできるようになります。

種明かしをしてしまうとそんなに威張ることじゃないんですが、(おまけに自分の思いつきでもないし…)、入力のリストを「ずらす」んですね…

たとえば:
(\a -> zip a a) [1..]
とやると、当然のごとく:
[(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10), ..]
となりますね…

これをちょっとひねって、

(\x -> zip x (tail x)) [1..]

とやると何が起きるでしょう?zipにわたる2つ目のリストは先頭要素が取り除かれているので、ひとつ「ずれて」いますね…ということで
[(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10),(10,11), ..]
となります。

とすれば、takeWhile2 は次のようにも書けることになりますね…

takeWhile2 :: (a -> a -> bool) -> [a] -> [a]
takeWhile2 f a = map snd $ takeWhile (uncurry . f) $ (\x -> zip x (tail x)) a

ただ、takeWhile2でちょっと悩ましいのはtakeされるのが 評価関数の第一パラメタにわたる値なのか第二パラメタにわたる値なのか、どっちがいいのかは結構その場その場で変わってきそうなところのような気もしますね…

この、「ずらす」トリックは面白いんですが、タプルを使う分効率が悪くなるようにも思われます…

今日はこの辺で。
ではでは。