第5章 リスト内包表記

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)