C#だとLINQだけど、Haskellなどの関数型言語やPythonではリスト内包。これも書き方さえ覚えてしまえばそんなに難しい話じゃない。 Haskellではforとか書かないので、やっぱり数学っぽい。でもやってることはforと同じ。
[(x, y) | x <- [1, 2, 3], y <- [4, 5]]
と、リストの中にバー(|)を置いて、右側に生成器を書く。生成器を複数並べると、ネストしたforみたいになる。(最も右側の生成器が最も頻繁に変化するというあたりもforと同じ。)
リスト内包でもワイルドカード(_)が使える。
first ps = [x | (x, _) <- ps]
でもこれ、ワイルドカードである意味はあるのかな?
first ps = [x | (x, y) <- ps]
これでも同じじゃないかと思うけど……それとも遅延評価されるかどうかが変わるのだろうか?
ガード
リスト内包のガードはフィルタになる。LINQでいうところのwhere、Pythonのリスト内包で言うところのif。 Haskellだとカンマ(,)で区切って論理式を書くようだけど、分かりやすいのかこれ?関数定義のガードはバー(|)を伴うけど、リスト内包ではすでにバーを使ってしまっているからカンマ区切りって、なんだかなあ。
factors n = [x | x <- [1..n], n `mod` x == 0]
書いてしまえば、短くまとまっているけどね。
教科書では、factorsからprime、primeからprimesを作っているので、写経してみる。
factors n = [x | x <- [1..n], n `mod` x == 0] prime n = factors n == [1, n] primes n = [x | x <- [2..n], prime x]
zip関数
自分で書くとこんな感じか?(復習を兼ねて、cons演算子とパターンマッチで)
zip' _ [] = [] zip' [] _ = [] zip' xs ys = (head xs, head ys) : zip' (tail xs) (tail ys)
教科書では、zipからpairsを作り、pairsからsortedを作っているので、写経してみる。
pairs xs = zip xs (tail xs) sorted xs = and [x <= y | (x, y) <- pairs xs]
なるほど、and関数(とor関数)にはリストを食わせるのか。逆に言うとこいつらは2項演算子じゃないんだな。
文字列の内包表記
ここで、Stringは[Char]であることが示される。気づいてたけど。
ただし String is a [Char]なのはいいけど、逆はどうなんだろう。つまり、Stringと[Char]は同値なのか?というのが気になったので、試してみた。
abc :: a -> String abc _ = ['a', 'b', 'c']
という関数を書いてみたけど普通に使えたので、Stringは[Char]のエイリアスなんだろうと理解した。
シーザー暗号
これも写経する。
import Data.Char table = [8.2, 1.5, 2.8, 4.3, 12.7, 2.2, 2.0, 6.1, 7.0, 0.2, 0.8, 4.0, 2.4, 6.7, 7.5, 1.9, 0.1, 6.0, 6.3, 9.1, 2.8, 1.0, 2.4, 0.2, 2.0, 0.1] let2int c = ord c - ord 'a' int2let n = chr (ord 'a' + n) shift n c | isLower c = int2let ((let2int c + n) `mod` 26) | otherwise = c encode n xs = [shift n x | x <- xs] lowers xs = length [x | x <- xs, isLower x] count x xs = length [x' | x' <- xs, x == x'] percent n m = (fromIntegral n / fromIntegral m) * 100 freqs xs = [percent (count x xs) n | x <- ['a'..'z']] where n = lowers xs chisqr os es = sum [((o - e) ^ 2) / e | (o, e) <- zip os es] rotate n xs = drop n xs ++ take n xs positions x xs = [i | (x', i) <- zip xs [0..n], x == x'] where n = length xs - 1 crack xs = encode (-factor) xs where factor = head (positions (minimum chitab) chitab) chitab = [chisqr (rotate n table') table | n <- [0..25]] table' = freqs xs
うわ、動いた。すごいけど気持ち悪っ!
演習問題
問題1
sum [n^2 | n <- [1..100]]
問題2
replicate n x = [x | _ <- [1..n]]
問題3
pyths n = [(x, y, z) | x <- [1..n], y <- [1..n], z <- [1..n], x^2 + y^2 == z^2]
問題4
factors n = [x | x <- [1..n], n `mod` x == 0] perfects n = [x | x <- [1..n], sum (factors x) == 2 * x]
問題5
concat [[(x,y) | y <- [4,5,6]] | x <- [1,2,3]]
問題6
find k t = [v | (k', v) <- t, k == k'] positions x xs = find x (zip xs [0..n]) where n = length xs - 1
問題7
scalarproduct xs ys = sum [x * y | (x, y) <- zip xs ys]
問題8
上記シーザー暗号の差分だけ。面倒だったからmap関数を使ったけど、いいよね。教科書にもさらっと出てきたし。
let2int' b c = ord c - ord b int2let' b n = chr (ord b + n) shift' b n c = int2let' b ((let2int' b c + n) `mod` 26) shift n c | isLower c = shift' 'a' n c | isUpper c = shift' 'A' n c | otherwise = c crack xs = encode (-factor) xs where factor = head (positions (minimum chitab) chitab) chitab = [chisqr (rotate n table') table | n <- [0..25]] table' = freqs (map toLower xs)