myInits (と myTails)

IO () さんから無限リストに対応できてない,と指摘を受けた myInits。これもできたと思う。

 cf. id:takatoh:20060513:exercise

昨日のは length を使って結果のリストの長さを決めてしまったのがいけなかった。これじゃ無限リストには対応できない。で,はじめは下の myTails と同様の考えで foldl を使って

myInits = foldl (\a b -> a ++ [last a ++ [b]]) [[]]

こうしたんだけど,やっぱり無限リストには対応できなかった。last を使ってるのがいけないのかな。
結局 zipWith を使った。

myInits xs = [] : zipWith (\a b -> a ++ [b]) (myInits xs) xs

これなら無限リストにも対応できるはずだし,zipWith はリストの終わりがくれば止まってくれる。さて,試してみよう。

*Main> myInits "abcde"
["","a","ab","abc","abcd","abcde"]
*Main> myInits ""
[""]
*Main> take 10 $ myInits [1..]
[[],[1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5],[1,2,3,4,5,6],[1,2,3,4,5,6,7],[1,2,3
,4,5,6,7,8],[1,2,3,4,5,6,7,8,9]]

よし,これならOK。ちゃんと無限リストに対応できてるし,先頭には空リストも付いてるな。ちなみに inits はこうだ。

Prelude List> inits "abcde"
["","a","ab","abc","abcd","abcde"]
Prelude List> inits ""
[""]
Prelude List> take 10 $ inits [1..]
[[],[1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5],[1,2,3,4,5,6],[1,2,3,4,5,6,7],[1,2,3
,4,5,6,7,8],[1,2,3,4,5,6,7,8,9]]

もう一つ,myTails。こっちはリストの最後に空リストがないところが違ってた。

myTails = foldr (\a b -> (a:head b):b) [[]]

結果。

*Main> myTails "abcde"
["abcde","bcde","cde","de","e",""]
*Main> myTails ""
[""]

ちなみに tails はこう。おんなじ。

Prelude List> tails "abcde"
["abcde","bcde","cde","de","e",""]
Prelude List> tails ""
[""]

OK。

パスカルの三角形

もう一つ思いついた。といっても「次の行」を作るところは一緒なんだけど。

 cf. id:takatoh:20060512:pascal (コメント欄も参照)

pascalTriangle = iterate (\xs -> zipWith (+) (0:xs) (xs ++ [0])) [1]

takeAndPut n = (((putStr . unlines) . map show) . take n)

結果。

*Main> takeAndPut 10 pascalTriangle
[1]
[1,1]
[1,2,1]
[1,3,3,1]
[1,4,6,4,1]
[1,5,10,10,5,1]
[1,6,15,20,15,6,1]
[1,7,21,35,35,21,7,1]
[1,8,28,56,70,56,28,8,1]
[1,9,36,84,126,126,84,36,9,1]

練習問題

入門Haskell―はじめて学ぶ関数型言語」 p.78 より。

(1) filter,inits,tails,zip,zipWith を定義しなさい。

myFilter f [] = []
myFilter f (x:xs) | f x = x:myFilter f xs
                  | otherwise = myFilter f xs

myInits [] = []
myInits xs = map ((flip take) xs) [1..(length xs)]

myTails [] = []
myTails xs = map ((flip drop) xs) [0..(length xs -1)]

myZip _ [] = []
myZip [] _ = []
myZip (x:xs) (y:ys) = (x,y) : myZip xs ys

myZipWith f _ [] = []
myZipWith f [] _ = []
myZipWith f (x:xs) (y:ys) = f x y : myZipWith f xs ys

結果。

*Main> myFilter (> 0) [-2,-1,0,1,2]
[1,2]
*Main> myInits "abcde"
["a","ab","abc","abcd","abcde"]
*Main> myInits ""
[]
*Main> myTails "abcde"
["abcde","bcde","cde","de","e"]
*Main> myTails ""
[]
*Main> myZip [1,2,3] [1,2,3,4]
[(1,1),(2,2),(3,3)]
*Main> myZipWith (+) [1,2,3] [1,2,3,4]
[2,4,6]

myZip,myZipWith の余った要素が無視されるのは正しい動作。

(2) sum,product,and,or のそれぞれを fold 系を使って定義しなさい。

mySum :: (Num a) => [a] -> a
mySum = foldl (+) 0

myProduct :: (Num a) => [a] -> a
myProduct = foldl (*) 1

myAnd :: [Bool] -> Bool
myAnd = foldl (&&) True

myOr :: [Bool] -> Bool
myOr = foldl (||) False

結果。

*Main> mySum [1,2,3,4,5]
15
*Main> myProduct [1,2,3,4,5]
120
*Main> mySum []
0*Main> myProduct []
1
*Main> myAnd [True, False, True]
False
*Main> myAnd [True, True, True]
True
*Main> myOr [False, False, True]
True
*Main> myOr [False, False, False]
False

sum と product はそれぞれ 0,1 になる。

*Main> sum []
0*Main> product []
1

追記:
IO () さんから myInits が無限リストに対応できていないと指摘をもらった。コメント欄参照。
ああっ,本当だ。

*Main> take 10 $ map (take 1) $ myInits [1..]
GHC's heap exhausted: current limit is 268435456 bytes;
Use the `-M<size>' option to increase the total heap size.

ついでに気が付いたけど,inits の結果は先頭に空リストがあるじゃないか。この点でも違っている(これは「入門Haskell」も間違っている。p.75)。

Prelude List> inits "abcde"
["","a","ab","abc","abcd","abcde"]

えーと,もしかして tails も?

Prelude List> tails "abcde"
["abcde","bcde","cde","de","e",""]

……出直してきます。

練習問題(つづき)

入門Haskell―はじめて学ぶ関数型言語」 p.78 より。

(3) unlines,unwords を intersperse を使って定義しなさい。また使わずに定義しなさい。

まずは使う方から。

myUnlines = (concat . intersperse "\n")

myUnwords = (concat . intersperse " ")

結果。

*Main> myUnlines ["abc","def","ghi"]
"abc\ndef\nghi"
*Main> myUnwords ["abc","def","ghi"]
"abc def ghi"

OK……とおもったら,unlines は一番最後にも改行文字が付くのか。

*Main> unlines ["abc","def","ghi"]
"abc\ndef\nghi\n"

そしたらこうか。あんまりきれいじゃないなぁ。

myUnlines [] = ""
myUnlines list = (concat . intersperse "\n") list ++ "\n"
*Main> myUnlines ["abc","def","ghi"]
"abc\ndef\nghi\n"

OK。

intersperse を使わないほう。これは似たようなことを前(cf. id:takatoh:20060413)にやった。

myUnlines2 [] = ""
myUnlines2 (x:xs) = x ++ "\n" ++ myUnlines2 xs

myUnwords2 [] = ""
myUnwords2 (x:[]) = x
myUnwords2 (x:xs) = x ++ " " ++ myUnwords2 xs

結果。

*Main> myUnlines2 ["abc","def","ghi"]
"abc\ndef\nghi\n"
*Main> myUnwords2 ["abc","def","ghi"]
"abc def ghi"

こっちはすんなりOK。

両替の組み合わせは?

id:a-san さんの両替するのに何通りあるか?に刺激されて,両替の組み合わせを列挙する enumChange をつくってみた。Maybe の扱いでちょっと苦労したよ。

 cf. http://d.hatena.ne.jp/a-san/20060508#p1

enumChange coins amount = map peel (cc amount 0)
    where
        cc amount kindsOfCoins
            | amount == 0 = [Just []]
            | (amount < 0) = [Nothing] | kindsOfCoins >= length coins = [Nothing]
            | otherwise =
                filterN ((cc amount (kindsOfCoins + 1)) ++
                       (divide faceOfCoin (cc (amount - faceOfCoins) kindsOfCoins)))
            where
                faceOfCoin = coins !! kindsOfCoins

divide a = map divide'
    where
        divide' x = case x of
            Just m -> Just (a:m)
            Nothing -> Nothing

filterN list = filter (\x -> x /= Nothing) list

peel x = case x of
             Just m -> m
             Nothing -> error "`Nothing' is found."

実行。

*Main> enumChange [10,5,1] 10
[[1,1,1,1,1,1,1,1,1,1],[5,1,1,1,1,1],[5,5],[10]]
*Main> length $ enumChange [10,5,1] 10
4

countChange は id:a-san さんのもの。

*Main> length $ enumChange [500,100,50,10,5,1] 100
159
*Main> countChange [500,100,50,10,5,1] 100
159
*Main> length $ enumChange [50,25,10,5,1] 100
292
*Main> countChange [50,25,10,5,1] 100
292
*Main> enumChange [500,100,50,10,5,1] 100
[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[5,
5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1],[5,5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[5,5,5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
(以下略)

いけてるみたいだな。

以下,自分用のメモ。

  • cc は両替したコインの組み合わせのリストを返す。
  • amount == 0 ならうまくいった組み合わせなので,リストの終端としての空リストを返す。
  • (amont < 0),kindsOfCoins >= length coins のときはその組み合わせがうまくいかないときなので Nothing。
  • (cc amount (kindsOfCoins + 1)) は kindsOfCoins のコインを1枚も使わない場合の組み合わせ。返ってくるリストには Nothing が混じっているから filterN で取り除いてやる。
  • (divide …) は kindsOfCoins のコインを1枚以上使う場合の組み合わせ。これは kindsOfCoins のコインを0枚以上使って (amount – faceOfCoin) を両替する場合の組み合わせのそれぞれに,kindsOfCoins を1枚追加して求めている。
  • cc の返すリストは Maybe [a] のリストだから peel で Maybe をはずす。

追記:
Data.Maybe に catMaybes という関数があった。わざわざ peel を作んなくてもよかったのか。
GHCi で使うなら :module Data.Maybe,プログラムで使うなら import Data.Maybe しておく。

Prelude Data.Maybe> :type catMaybes
catMaybes :: [Maybe a] -> [a]
Prelude Data.Maybe> catMaybes [Just 1, Just 2, Just 3]
[1,2,3]

Nothing は無視してくれるみたいだ。

Prelude Data.Maybe> catMaybes [Just 1, Nothing, Just 3]
[1,3]

ってことは filterN もいらないのか……orz

パスカルの三角形

id:hyuki さんのを見て。

 cf. http://sicp.g.hatena.ne.jp/hyuki/20060512/pascal

といっても Scheme はよくわからんので Ruby版(id:rubyco:20060429:pascal)を見ながら書いた。

combination n k | k == 1 = 1
                | k == n = 1
                | otherwise = (combination (n-1) k) + (combination (n-1) (k-1))

line n = map (combination n) [1..n]

pascalTriangle = do mapM putStrLn (map (show . line) [1..10])

実行。

*Main> pascalTriangle
[1]
[1,1]
[1,2,1]
[1,3,3,1]
[1,4,6,4,1]
[1,5,10,10,5,1]
[1,6,15,20,15,6,1]
[1,7,21,35,35,21,7,1]
[1,8,28,56,70,56,28,8,1]
[1,9,36,84,126,126,84,36,9,1]

練習問題

入門Haskell―はじめて学ぶ関数型言語」 p.73 より。
①は面倒なだけなのでパス。

②foldr の定義を書きなさい。

foldr は後ろから引数の関数を適用するんだからこうだろう

myFoldr f a x:[] = f x a
myFoldr f a (x:xs) = f x (myFoldr f a xs)

実行。

*Main> myFoldr (++) "d" ["a","b","c"]
"abcd"
*Main> foldr (++) "d" ["a","b","c"]
"abcd"

OK。ちゃんとできてるな。

③reverse は,リストを逆転させる関数です。たとえば reverse [1,2,3] は [3,2,1] になります。この reverse を定義しなさい。

myReverse [] = []
myReverse (x:xs) = myReverse xs ++ [x]
*Main> myReverse "abcde"
"edcba"

こちらもOK。

関数の合成

2つの関数を f と g とすれば f . g と単純に行くのは g の引数が1つの場合だけ。2つ以上の引数をとる場合にはちょっと複雑になる。次のような関数で確かめてみる。

f a = a:[]

g a = a:[]
g2 a b = a:b:[]
g3 a b c = a:b:c:[]

まずは簡単な f と g

*Main> :t f . g
f . g :: a -> [[a]]

引数を2つとる g2 に対して同じようにやると

*Main> :t (f . g2)
(f . g2) :: a -> [a -> [a]]

こんな型になって2つの引数をうまく追い出せない。

*Main> (f . g2) 1 2

<interactive>:1:0:
    Couldn't match `[a -> [a]]' against `t -> t1'
      Expected type: [a -> [a]]
      Inferred type: t -> t1
    Probable cause: `(f . g2)' is applied to too many arguments in the call
        ((f . g2) 1 2)
    In the definition of `it': it = (f . g2) 1 2

追い出してやるには,関数合成を2段階にする。

*Main> :t ((f .) . g2)
((f .) . g2) :: a -> a -> [[a]]
*Main> ((f .) . g2) 1 2
[[1,2]]

引数が3つの場合には3段階。

*Main> :t (((f .) .) . g3)
(((f .) .) . g3) :: a -> a -> a -> [[a]]
*Main> (((f .) .) . g3) 1 2 3
[[1,2,3]]

逆に1つ目の関数のほうが複数引数の時は単純に合成できる。

*Main> :t g3 . f
g3 . f :: a -> [a] -> [a] -> [[a]]

ただし,引数のほうがややこしい。上の例で行くと1つ目の引数は f の引数,2つ目以降が g3 の2番目3番目の引数となる。こうすればわかりやすい。

g3 (f a) b c

実行。

*Main> (g3 . f) 1 [2] [3]
[[1],[2],[3]]

じゃあ,引数が3つと2つならどうだ。

*Main> :t (g3 .) . g2
(g3 .) . g2 :: a -> a -> [a] -> [a] -> [[a]]
*Main> ((g3 .) . g2) 1 2 [3] [4]
[[1,2],[3],[4]]

ああ,ややこしい。

練習問題

今日は目先を変えて練習問題をやろう。
入門Haskell―はじめて学ぶ関数型言語」 p.72 より。

①前ページの実装から,takeとdropに大きな値や負の値が入った場合の対処をしなさい。

前ページの実装とはこれ。Prelude の関数とかぶってはいけないので名前は変えてある。

mytake 0 _ = []
mytake n (x:xs) = x : mytake (n-1) xs

mydrop 0 xs = xs
mydrop n (x:xs) = mydrop (n-1) xs

mytake に大きな値を入れるとリストそのもの,負の値を入れると空リストが返るようにする。逆に mydrop に大きな値を入れると空リスト,負の値を入れるとリストそのものが返るようにする。

mytake _ [] = []
mytake 0 _ = []
mytake n (x:xs) | n < 0 = []
               | otherwise = x : mytake (n-1) xs

mydrop _ [] = []
mydrop 0 xs = xs
mydrop n (x:xs) | n < 0 = x:xs
                | otherwise = mydrop (n-1) xs 

もっとすっきり書けそうだけどまぁいいか。実行結果。

*Main> mytake 10 [0,1,2,3,4,5]
[0,1,2,3,4,5]
*Main> mytake (-2) [0,1,2,3,4,5]
[]
*Main> mydrop 10 [0,1,2,3,4,5]
[]
*Main> mydrop (-2) [0,1,2,3,4,5]
[0,1,2,3,4,5]

ふたつめ。

②take と drop を同時に実行する splitAt :: Int -> [a] -> ([a], [a]) があります。たとえば
splitAt 2 [1,2,3,4] — ([1,2], [3,4])
のように,結果のタプルの第1要素が take ,第2要素が drop になります。この splitAt を定義しなさい。また,take と drop を splitAt を使って定義し直しなさい。

まずは splitAt のほうから(名前は変えてある)。

mysplitAt _ [] = ([], [])
mysplitAt n xs = ((take n xs), (drop n xs))

結果。

*Main> mysplitAt 2 [1,2,3,4]
([1,2],[3,4])

よし。じゃ,これを使って mytake と mydrop を定義し直すと

mytake n xs = fst $ mysplitAt n xs

mydrop n xs = snd $ mysplitAt n xs

結果は

*Main> mytake 3 [1,2,3,4,5]
[1,2,3]
*Main> mydrop 3 [1,2,3,4,5]
[4,5]

もうひとつ。

③takeWhile は,(a -> Bool) -> [a] -> [a] という型です。takeと似ていますが,決まった数だけとるのではなく,要素を計算し たら真である限りtakeし,一度でも失敗したら残りは返しません。これを定義しなさい。

こんなんでどうかな。

mytakeWhile _ [] = []
mytakeWhile f (x:xs) | f x = x : mytakeWhile f xs
                     | otherwise = []
*Main> mytakeWhile (\x -> x > 0) [3,2,1,0,-1,-2,-3]
[3,2,1]
*Main> mytakeWhile (\x -> x < 0) [3,2,1,0,-1,-2,-3]
[]