programming haskell chapter 11 切符番号選び
DESCRIPTION
Programming Haskell Chapter 11 切符番号選びについてTRANSCRIPT
プログラミング Haskell 11 章
切符番号選び@dekosuke
2
数字遊び @ twitter切符番号選び切符番号選びの高速化
概略
3
数字遊び
ランダムな4つの数字から、 10 を作ろう!
4
数字遊び
Easy! => 3+2+7-2
5
数字遊び
TL を眺めてみると、問題によっては、難易度高い・・・
6
数字遊び
累乗で解決!
7
数字遊び
さらに難易度上昇
8
数字遊び
絶対値とルートで解決!?・・・
9
数字遊び
絶対値とルートで解決!?・・・
10
数字遊び
上の Twitter の数字遊びは、使える演算が明示されていないあいまいさがある(それが面白さにつながる点も)
使える演算をちゃんと決めよう!
11
切符番号選びのルール
正数のリストが与えられるリストから好きな数だけ選んで正数を使
い、四則演算を行える計算途中で0以下になってはいけない
12
切符番号選びのルール
[Int] -> [(Expr, Int)]
整数のリストから、四則演算で作られる式とその答えのリストが作られる欲しい数は、その中に「ある」か「ない」かのどちらか
13
切符番号選びのルール
というわけで、切符番号選びのプログラムを書きましょう!
14
切符番号選び
data Op = Add | Sub | Mul | Div-- 演算
valid :: Op -> Int -> Int -> Boolapply :: Op -> Int -> Int -> Int-- 演算が正当かどうかと、演算を行う関数--Maybe?...
15
切符番号選び
data Expr = Val Int | App Op Expr Expr-- 式
eval :: Expr -> [Int]-- 式の評価 ( 実質 Maybe)
-- 整数の組み合わせを作るため、次から色々関数を定義します--Val は適宜省略します
16
切符番号選び
subs :: [a] -> [[a]]subs [] = [[]]subs (x:xs) = yss ++ map (x:) yss where yss = subs xs
-- リストの全ての部分集合を作る-- 集合のべき乗 (2^X)--subs [1, 2] = [[], [2], [1], [1,2]]
17
切符番号選び
perms :: [a] -> [[a]]perms [] = [[]]perms (x:xs) = concat (map (interleave x) (perms xs))
-- 集合の permutation( 並び替え )
18
切符番号選び
choices :: [a] -> [[a]]choices xs = concat (map perms (subs xs))
-- 全ての部分集合を含んだ順列を作る
19
切符番号選び
solution :: Expr -> [Int] -> Int -> Boolsolution e ns n = elem (values e) (choices ns) && eval e == [n]
-- 式 e を構成する数値がリスト ns に含まれている かつ 式 e を評価すると値 n になる
20
切符番号選び
全ての解答を探す関数 solutions を作りたい
solutions :: [Int] -> Int -> [Expr]--solutions ( 数のリスト ) 正解値 -> 数のリストが生成元の式で、評価すると正解値になるもの-- solutions [1,2,3] 3 = [Val 3, Add 1 2, Add 2 1]
21
切符番号選び
split :: [a] -> [([a],[a])]split [] = []split [_] = []split (x:xs) = ([x],xs):[(x:ls,rs)|(ls,rs) <- split xs]
-- 集合を全ての場所で2つに割る-- split [1,2,3,4] = [([1],[2,3,4]), ([1,2],[3,4]), ([1,2,3],[4])]
22
切符番号選び
split :: [a] -> [([a],[a])]split [] = []split [_] = []split (x:xs) = ([x],xs):[(x:ls,rs)|(ls,rs) <- split xs]
-- 集合を全ての場所で2つに割る-- split [1,2,3,4] = [([1],[2,3,4]), ([1,2],[3,4]), ([1,2,3],[4])]
23
切符番号選び
combine :: Expr -> Expr -> [Expr]combine l r = [App o l r | o<-ops]ops :: [Op]ops = [Add,Sub,Mul,Div]
--2 つの式にすべての演算を適用--[Add 式 1 式 2, Sub 式 1 式 2, Mul 式 1 式 2, Div 式 1 式 2]
24
切符番号選び
exprs :: [Int] -> [Expr]exprs [n] = [Val n]exprs ns = [e | (ls, rs) <- split ns, l<-exprs ls, r<-exprs rs, e<-combine l r]
-- 数値のリストを全て一回ずつ使った式すべてを返す ( 本当はこれは遅い ( 後述 ) )
25
完成
solutions :: [Int] -> Int -> [Expr]solutions ns n = [e|ns' <- choices ns, e<-exprs ns', eval e==[n]]-- 与えられた数を生成する式をすべて探す
が、遅い… (4 分 @ モダンなマシン )(Sub 1 2) <- こういう式が途中に出たら打ち切りたい
26
高速化
type Result = (Expr, Int)
combine' :: Result -> Result -> [Result]combine' (l,x) (r,y) = [(App o l r, apply o x y)|o <- ops, valid o x y]
--2 つの Expr に対して、 valid な演算すべてを適用
27
高速化
results :: [Int] -> [Result]results [] = []results [n] = [(Val n, n) | n>0 ]results ns = [res | (ls,rs) <- split ns, lx<-results ls, ry <-results rs, res<-combine' lx ry]
-- 数値のリストを、すべて使った式を作って評価する-- [1,2] -> [(Add 1 2, 3), (Mul 1 2, 2)]
28
高速化
solutions' :: [Int] -> Int -> [Expr]solutions' ns n = [e|ns' <- choices ns, (e,m)<-results ns' , m==n]
-- 式を作るうえで valid でない演算は早めに打ち切る--10 数秒 @ モダンなマシン
29
高速化
さらに工夫を…・足し算や掛け算は可換なので (Add 2 3) と (Add 3 2) は片方でいい・ (Mul x 1) は自明なので除去これらの条件を valid に入れ込む
30
高速化
・ solutions は元の数のリストに対して階乗で計算量が増大する・組合せ爆発・式の途中で計算を打ち切ると、組合せの増加速度を抑えられる→小さな工夫が指数的に効く