12 July, 2006

[Haskell] LL Ring : Round 2

LL Ringの、キミならどう書く 2.0 - ROUND 2 -をHaskellで解いてみた ... 特に工夫した訳ではないので単に解いただけに過ぎないのではあるが。

下記のような感じでhugsの上で動作確認しつつ小さな関数をくみ上げて実装。

f :: Int -> Int
f n
| n == 1 = 1
| even n = f (n `div` 2)
| otherwise = f (3 * n + 1)

f2 :: (Int,Int) -> (Int,Int)
f2 (n,k)
| n == 1 = (n,k)
| even n = f2 (n `div` 2, k + 1)
| otherwise = f2 (3 * n + 1, k + 1)

g :: Int -> Int
g n = snd ( f2 (n, 1))

g2 :: Int -> (Int,Int)
g2 n = (n, g n)

gs :: Int -> [(Int,Int)]
gs n = map g2 [1 .. n]

h2 :: [(Int,Int)] -> (Int,Int) -> (Int,Int)
h2 [] m = m
h2 (x:xs) m = case (snd x > snd m) of
True -> h2 xs x
False -> h2 xs m

h :: Int -> Int
h n = fst (h2 (gs n) (0,0))

main = do args <- getArgs
mapM_ (putStrLn . show . h . read) args


これをhugsの上でテスト。

Main> g2 3
(3,8)
Main> gs 10
[(1,1),(2,2),(3,8),(4,3),(5,6),(6,9),(7,17),(8,4),(9,20),(10,7)]
Main> h2 $$ (0,0)
(9,20)
Main> h 10
9
Main>


ghcでコンパイルして、iBook G4 で実行して時間測定。

% /usr/bin/time ./collatz 100
97
0.07 real 0.00 user 0.01 sys
% /usr/bin/time ./collatz 1000
871
0.18 real 0.10 user 0.01 sys
% /usr/bin/time ./collatz 10000
6171
1.28 real 1.13 user 0.04 sys
% /usr/bin/time ./collatz 100000
77031
14.64 real 13.78 user 0.28 sys


---
7/12追記
はてなのHaskellグループでみつけた回答→collatz予想 @ haskellのある暮らし
うーむ、奇麗なコードだ。あと、明示的にHashとかを使ってメモ化すると早い、ということの様だ。
遅延評価で再計算しないはずだと考えると、明示的にメモ化するメリットはどこにあるのだろうか。検索性能?

09 July, 2006

[Haskell] Exercise 5.8 - 5.11

LIst comprehension notation の練習
○ Exercise 5.8

doubleAll :: [Int] -> [Int]
doubleAll xs = [2*x | x <- xs]

Main> doubleAll [1,2,3]
[2,4,6]
Main> doubleAll []
[]


○ Exercise 5.9

import Char

toupper :: Char -> Char
toupper c
| ('a' <= c) && (c <= 'z') = chr ( ord c + ord 'A' - ord 'a')
| otherwise = c

isLetters :: Char -> Bool
isLetters c
| ('a' <= c) && (c <= 'z') = True
| ('A' <= c) && (c <= 'Z') = True
| otherwise = False

capitalize :: String -> String
capitalize s = [toupper c | c <- s]

capitalizeLetters :: String -> String
capitalizeLetters s = [toupper c | c <- s, isLetters c]

Main> capitalize "aabbCdE"
"AABBCDE"
Main> capitalize "aabbC--dE"
"AABBC--DE"
Main> capitalizeLetters "aabbC--dE"
"AABBCDE"
Main>

○ Exercise 5.10

divisors :: Int -> [Int]
divisors n = [x | x <- [1 .. n], n `mod` x == 0]

isPrime :: Int -> Bool
isPrime n = (length (divisors n)) == 2

Main> divisors 12
[1,2,3,4,6,12]
Main> divisors 7
[1,7]
Main> isPrime 12
False
Main> isPrime 7
True
Main>

○ Exercise 5.11

matches :: Int -> [Int] -> [Int]
matches x ys = [y | y<- ys, y == x]

elem :: Int -> [Int] -> Bool
elem x ys = (length (matches x ys)) /= 0

Main> matches 1 [1,2,1,3,1,4]
[1,1,1]
Main> elem 1 [1,2,1,3,1,4]
True
Main> matches 1 [2,3,4]
[]
Main> elem 1 [2,3,4]
False
Main>

07 July, 2006

[Haskell] Comparing Haskell and OOP language

 Smalltalkとかのオブジェクト指向な言語だと、適切なコレクションのクラスをうまく使うことが重要、という気がする。例えば先の問題Bとかで重複を避けて数えるなら、BagではなくSetを使うのが簡単な方法だろう。
 対するにHaskellとかの場合は、とりあえずリストにして、mapとかのリストに作用させる高階関数をうまく使う事が重要っぽい。その一方で、リストは単なるBagみたいなものでそれ自体には特筆すべき機能がないように思うのだが。

 あと、幾つもの関数を . や $ で繋ぐのは、なんかUnixのパイプみたいな感じだ。簡単な事をする関数を繋いで、という感じ。
 その見方で言うと、リストとかが標準入出力みたいな感じだろうか。リストから値を取って、加工してリストに入れる、みたいな。

[Haskell] ICPC 2006 Domestic, Problem B

小さな関数を書いて繋ぎ合わせて答えを出す事にする。これもmainが書けてないのだがまぁ問題自体は解けたという事で。

まず、リストから重複を除いて整列させる関数。整列は実は重要では無いが、重複は除きたい。

-- sort & uniq
--
sortuniq :: Ord a => [a] -> [a]
sortuniq [] = []
sortuniq [x] = [x]
sortuniq (p:xs) = sortuniq smaller ++ [p] ++ sortuniq larger
where
smaller = [x | x <- xs, x < p]
larger = [x | x <- xs, x > p]

Main> sortuniq [1,2,1,3,1,4]
[1,2,3,4]
Main> sortuniq [5,5,1,2,1,3,1,4]
[1,2,3,4,5]

次いで、電車を2つに分けたリストを作る。

-- spritStr
--
splitStr :: String -> [(String, String)]
splitStr s = map splitAtOnS [1 .. (length s - 1)]
where
splitAtOnS n = splitAt n s
-- splitAt :: Int -> [a] -> ([a],[a])

Main> splitStr "abcd"
[("a","bcd"),("ab","cd"),("abc","d")]

次いで、各ペアについて順序を反転するか否かで4通りづつ作る。重複があればここで除く。

-- reversePair
--
reversePair :: (String,String) -> [(String,String)]
reversePair (sa, sb) = sortuniq [(sa,sb), (sa,rsb), (rsa,sb), (rsa,rsb)]
where
rsa = reverse sa
rsb = reverse sb

Main> map reversePair $$
[[("a","bcd"),("a","dcb")],[("ab","cd"),("ab","dc"),("ba","cd"),("ba","dc")],[("abc","d"),("cba","d")]]
Main> concat $$
[("a","bcd"),("a","dcb"),("ab","cd"),("ab","dc"),("ba","cd"),("ba","dc"),("abc","d"),("cba","d")]

次いで、それらを連結する。

-- connectPair
--
connectPair :: (String,String) -> [String]
connectPair (sa,sb) = [sa ++ sb, sb ++ sa]


Main> map connectPair $$
[["abcd","bcda"],["adcb","dcba"],["abcd","cdab"],["abdc","dcab"],["bacd","cdba"],["badc","dcba"],["abcd","dabc"],["cbad","dcba"]]
Main> concat $$
["abcd","bcda","adcb","dcba","abcd","cdab","abdc","dcab","bacd","cdba","badc","dcba","abcd","dabc","cbad","dcba"]

次いで重複を除く。

trainPattern :: String -> [String]
trainPattern s = sortuniq $ concat $ map connectPair $ concat $ map reversePair (splitStr s)


Main> sortuniq $$
["abcd","abdc","adcb","bacd","badc","bcda","cbad","cdab","cdba","dabc","dcab","dcba"]
Main>

最後にリストの数を数える。

-- answer
--
answer :: String -> Int
answer s = length $ trainPattern s

Main> answer "aa"
1
Main> answer "abba"
6
Main> answer "abcd"
12
Main> answer "abcde"
18
Main>

05 July, 2006

[Haskell] ICPC 2006 Domestic, Problem A

2006-07-04 @ HaHaHa!International Collegiate Programming Contest : Domestic Contest 2006の存在を知る。

HaHaHa! の答えを見ないで簡単そうな問題を解いてみる事にする。とりあえず問題Aが簡単そう。
...うぅぅむ、入出力のところが判らない。「ふつける」か「入門Haskell」 を読まないと駄目かなぁ。
エラトステネスの篩の部分はとりあえず教科書からコピペ。(まぁ慣用句みたいなものだし...)

primes :: [Int]
primes = 2:(sieve [3, 5 ..])
where sieve (p:xs) = p: (sieve [ x | x <- xs, x `mod` p /= 0])

arithSeriesPrimes :: (Int, Int) -> [Int]
arithSeriesPrimes (a,d) = [ x | x <- primes, x >= a, (x-a) `mod` d == 0]

answer :: (Int, Int, Int) -> Int
answer (a, d, n) = last $ take n $ arithSeriesPrimes (a,d)

Prelude> :load acm2006a.hs
Compiling Main ( acm2006a.hs, interpreted )
Ok, modules loaded: Main.
*Main> answer (179,10,203) --- これは時間がかかった
6709
*Main> answer (179,10,203) --- これはすぐに答えが出た
6709
*Main> answer (271,37,39) --- これも割と早い?
12037
*Main> answer (367,186,151) --- これも時間がかかった
92809
*Main> answer (27,104,185) --- これは早い。
93523

primesは毎回最初から計算している訳ではなく、計算した所までは答えが記憶されているようだ。
うぅぅむ、入出力のところをちゃんと書ける様にしなくては。

一方で、この程度の問題ならJavaでもそんなに時間がかからずに書けるかも、とも思った。もっとパズル的な問題を解ける様にならねば。

04 July, 2006

[Haskell] HUnit

2006-07-03 HUnit@ のびのびなHaskell日記

 HUnit の使い方について書かれている。
 HUnit は慣れ親しんだ JUnit と同様の使い方なんで簡単に使えそうな気がする。

[Haskell] Exercise 5.5 - 5.7

○ Exercise 5.5
0.1は2進小数では無限小数になるので0.1*10=1にならない、というのが期待されていたのだと思うが、教科書の問題に関してはOKでした。

Main> [0, 0.1 .. 1]
[0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]

○ Exercise 5.6
[2, 3] は、2と3のリスト。
[[2, 3]]は、[2,3]を含むリスト。

Main> length [2,3]
2
Main> length [[2,3]]
1
Main> :type [[2,3]]
[[2,3]] :: Num a => [[a]]
Main>

○ Exercise 5.7
最後のは^Cを押すまで無限ループ。

Main> [2 .. 2]
[2]
Main> [2, 7 .. 4]
[2]
Main> [2, 2 .. 2]
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2^C{Interrupted!}

Main>

[Haskell] Exercise 5.1 - 5.4

○ Exercise 5.1
tuple の使い方を学ぶ

maxOccurs :: Int -> Int -> (Int,Int)
maxOccurs x y
| x == y = (x, 2)
| x > y = (x, 1)
| x < y = (y, 1)

Main> maxOccurs 3 5
(5,1)
Main> maxOccurs 5 3
(5,1)
Main> maxOccurs 5 5
(5,2)

maxThreeOccurs :: Int -> Int -> Int -> (Int,Int)
maxThreeOccurs x y z
| m == z = (m, n+1)
| m > z = (m, n)
| m < z = (z, 1)
where m = fst $ maxOccurs x y
n = snd $ maxOccurs x y

Main> maxThreeOccurs 3 4 5
(5,1)
Main> maxThreeOccurs 3 5 4
(5,1)
Main> maxThreeOccurs 4 3 5
(5,1)
Main> maxThreeOccurs 4 5 3
(5,1)
Main> maxThreeOccurs 5 3 4
(5,1)
Main> maxThreeOccurs 5 4 3
(5,1)
Main> maxThreeOccurs 3 4 4
(4,2)
Main> maxThreeOccurs 4 3 4
(4,2)
Main> maxThreeOccurs 4 4 3
(4,2)
Main> maxThreeOccurs 4 4 4
(4,3)
Main>

○ Exercise 5.2
listをsortすれば終わりのような問題ではあるが、tupleを扱う練習なので素直に。

maxThree, middleThree, minThree :: (Int, Int, Int) -> Int
maxThree (x, y, z)
| x >= y && x >= z = x
| y >= x && y >= z = y
| otherwise = z

minThree (x, y, z)
| x <= y && x <= z = x
| y <= x && y <= z = y
| otherwise = z

middleThree (x, y, z) = x + y + z - maxThree (x,y,z) - minThree (x,y,z)

orderTriple :: (Int, Int, Int) -> (Int, Int, Int)
orderTriple (x,y,z) = (minThree (x,y,z), middleThree (x,y,z), maxThree (x,y,z))


Main> orderTriple (1,2,3)
(1,2,3)
Main> orderTriple (1,3,2)
(1,2,3)
Main> orderTriple (2,1,3)
(1,2,3)
Main> orderTriple (2,3,1)
(1,2,3)
Main> orderTriple (3,1,2)
(1,2,3)
Main> orderTriple (3,2,1)
(1,2,3)
Main> orderTriple (1,1,2)
(1,1,2)
Main> orderTriple (1,2,1)
(1,1,2)
Main> orderTriple (2,1,1)
(1,1,2)
Main> orderTriple (1,1,1)
(1,1,1)
Main>

○ Exercise 5.3, 5.4
直線 ax+by+c=0 がx軸と交わる点は、x=-c/a if a /= 0。
a /= 0 --> (-c/a, True)を、a == 0 --> (0.0, false) を返す関数を作る事にする。

-- find x for ax+by+c=0 where y=0
-- x = -c/a
findX :: (Float, Float, Float) -> (Float, Bool)
findX (a, b, c)
| a /= 0.0 = ((-c) / a, True)
| a == 0.0 = (0.0, False)

Main> findX (1.0, 1.0, 1.0)
(-1.0,True)
Main> findX (1.0, 0.0, 1.0)
(-1.0,True)
Main> findX (0.0, 0.0, 1.0)
(0.0,False)
Main> findX (0.0, 1.0, 1.0)
(0.0,False)
Main> findX (1.0, 2.0, 3.0)
(-3.0,True)
Main>

01 July, 2006

[Haskell] Exercise 4.15 - 4.20

テストケースの自動生成を行うquickCheckについては、いつか。(現状ではテスト仕様を私がHaskellでうまく書けないので。)
○ Exercise 4.15
allEquals :: Int -> Int -> Int -> Bool
のテストなので、
allEquals 1 1 1, allEquals 1 1 2, allEquals 1 2 1, allEquals 2 1 1, allEquals 1 2 3
とかをテストする。

○ Exercise 4.16
allEqualsの実装solutionについて検討する。m+n=2p となるようなケース(最後のケース)でsolutionは誤る。

solution :: Int -> Int -> Int -> Bool
solution m n p = ((m+n+p)==3*p)

Main> solution 1 1 1
True
Main> solution 1 1 2
False
Main> solution 1 2 1
False
Main> solution 2 1 1
False
Main> solution 1 2 3
False
Main> solution 1 5 3
True

○ Exercise 4.17
allDifferent :: Int -> Int -> Int -> Bool
テストケースとしては、
allDifferent 1 1 1, allDifferent 1 1 2, allDifferent 1 2 1, allDifferent 2 1 1, allDifferent 1 2 3
とか。
○ Exercise 4.18
allDifferentの実装attemptについて。attempt 1 2 1で誤り。

attempt :: Int -> Int -> Int -> Bool
attempt m n p = (m /= n) && (n /= p)

Main> attempt 1 1 1
False
Main> attempt 1 1 2
False
Main> attempt 1 2 1
True
Main> attempt 2 1 1
False
Main> attempt 1 2 3
True
Main>

○ Exercise 4.19
関数自体はExercise3.14で作成している。
ケースとしては、
* 3つとも同じ値
* 平均以上が1つの場合。引数の入れ替えに対して対称。
* 平均以上が2つの場合。引数の入れ替えに対して対称。
を試す。

Main> howManyAboveAverage 1 1 1
0
Main> howManyAboveAverage 1 2 10
1
Main> howManyAboveAverage 1 10 2
1
Main> howManyAboveAverage 2 1 10
1
Main> howManyAboveAverage 2 10 1
1
Main> howManyAboveAverage 10 1 2
1
Main> howManyAboveAverage 10 2 1
1
Main> howManyAboveAverage 1 10 11
2
Main> howManyAboveAverage 1 11 10
2
Main> howManyAboveAverage 10 1 11
2
Main> howManyAboveAverage 10 11 1
2
Main> howManyAboveAverage 11 1 10
2
Main> howManyAboveAverage 11 10 1
2
Main>

○ Exercise 4.20
Exercise 4.14を参照の事。

[Haskell] Exercise 4.13, 4.14

○ Exercise 4.13
ユークリッドの互除法

import Prelude hiding (gcd)
gcd :: Int -> Int -> Int
gcd m n
| m < n = gcd n m
| m `mod` n == 0 = n
| otherwise = gcd n (m `mod` n)

Main> gcd 7 5
1
Main> gcd 12 18
6
Main>

○ Exercise 4.14

power2 :: Int -> Int
power2 n
| n == 0 = 1
| n == 1 = 2
| n `mod` 2 == 0 = square (power2 (n `div` 2))
| otherwise = 2 * square (power2 ((n-1) `div` 2))
where square x = x * x

Main> power2 0
1
Main> power2 1
2
Main> power2 2
4
Main> power2 3
8
Main> power2 4
16
Main> power2 5
32
Main>

[Haskell] Exercise 4.7 - 4.12

○ Exercise 4.7

mulFun :: (Int -> Int) -> Int -> Int
mulFun f n
| n == 0 = f 0
| n > 0 = f n * mulFun f (n-1)
| otherwise = error "mulFun only defined on natural numbers"

Main> mulFun (\x -> x + 1) (-1)

Program error: mulFun only defined on natural numbers

Main> mulFun (\x -> x + 1) 0
1
Main> mulFun (\x -> x + 1) 1
2
Main> mulFun (\x -> x + 1) 2
6
Main>

○ Exercise 4.8
ループカウンタを引数にする方法しか思いつかなかった。

sqrt :: Int -> Int
sqrt x
| x < 0 = error "sqrt defined only on natural number"
| otherwise = sqrt2 0 x

sqrt2 :: Int -> Int -> Int
sqrt2 n x
| n*n <= x && x < (n+1)*(n+1) = n
| otherwise = sqrt2 (n+1) x

Main> sqrt 0
0
Main> sqrt 1
1
Main> sqrt 15
3
Main> sqrt 16
4
Main> sqrt 17
4
Main> sqrt (-1)

Program error: sqrt defined only on natural number

Main>

○ Exercise 4.9

maxFun :: (Int -> Int) -> Int -> Int
maxFun f n
| n == 0 = f 0
| n > 0 = max (f n) (maxFun f (n-1))
| otherwise = error "maxFun defined only on natural number"

f :: Int -> Int
f 0 = 0
f 1 = 44
f 2 = 17
f _ = 0

Main> maxFun f 0
0
Main> maxFun f 1
44
Main> maxFun f 2
44
Main> maxFun f 3
44
Main>

○ Exercise 4.10

searchZero :: (Int -> Int) -> Int -> Bool
searchZero f n
| n < 0 = error "defined only on natural number"
| n == 0 = (f 0 == 0)
| otherwise = (f n == 0) || searchZero f (n-1)

Main> searchZero (\x -> x - 3) (-1)

Program error: defined only on natural number

Main> searchZero (\x -> x - 3) 0
False
Main> searchZero (\x -> x - 3) 1
False
Main> searchZero (\x -> x - 3) 3
True
Main> searchZero (\x -> x - 3) 4
True
Main>

同じようなことをリストを使えば簡単になる。

searchZero2 :: (Int -> Int) -> Int -> Bool
searchZero2 f n = any (\x -> f x == 0) [0 .. n]

Main> searchZero2 (\x -> x - 3) 2
False
Main> searchZero2 (\x -> x - 3) 3
True
Main> searchZero2 (\x -> x - 3) 4
True
Main>

○ Exercise 4.11
region 0 = 1
region 1 = region 1 + 1
region 2 = region 2 + 2 = region 0 + 1 + 2
よって
region n = region 0 + n(n+1)/2 = n(n+1)/2 + 1
と再帰を使わず明に書き下せる。
○ Exercise 4.12 [Harder]
教科書には、平面をn個の直線で分割すると幾つの領域に別れるか、という例が載っている。
この問題は、同様に考察して、空間をn個の平面で分割すると幾つの領域に別れるか、を考える。
region3 0 = 1, region3 1 = 2, region3 2 = 4, region3 3 = 8
ここまでは自明。

今、空間を3つの平面 p1, p2, p3で切ったとする。このp1, p2, p3 と全て交わる平面として p4を考える。p4の切り口はどうなっているかというと、3本の直線で平面を領域分けした状態になっているはず。この領域が切断で領域数が倍に増える。
よって、region3 4 = region3 3 + region 3の様に計算すれば良い。
計算してみると、region3 0 = 1と、教科書の region を使えば、region3 1, region3 2, region3 3も正しく解ける事が判る。
(同様に考えていけば、n次元空間の分割もnに関する数学的帰納法で解ける。)

なお、数学として公式まで求めている回答は、球は何個に分けられる?の千葉さんの回答を参照の事。(同じ様に考えている人がいるので、これで回答としてはいいのだと思う。)

region2 :: Int -> Int
region2 n
| n == 0 = 1
| n > 0 = region2 (n-1) + n
| otherwise = error "n<0"

region3 :: Int -> Int
region3 n
| n == 0 = 1
| n > 0 = region3 (n-1) + region2 (n-1)
| otherwise = 0

Main> region3 0
1
Main> region3 1
2
Main> region3 2
4
Main> region3 3
8
Main> region3 4
15
Main> region3 5
26
Main>

[Haskell] Exercise 4.5, 4.6

ようやく再帰の話とかが出てきました。

rangeProduct :: Int -> Int -> Int
rangeProduct m n
| m > n = 0
| m == n = n
| otherwise = m * rangeProduct (m+1) n

Main> rangeProduct 4 3
0
Main> rangeProduct 4 4
4
Main> rangeProduct 4 5
20
Main> rangeProduct 4 6
120
Main> rangeProduct (-3) (-1)
-6
Main> rangeProduct (-3) 3
0
Main>

fac を rangeProduct を使って定義する。負の引数に対してエラー表示と、0に対して 0! = 1 を返す様にする必要。

fac :: Int -> Int
fac n
| n < 0 = error "fac only defined on natural number"
| n == 0 = 1
| otherwise = rangeProduct 1 n

Main> fac (-1)

Program error: fac only defined on natural number

Main> fac 0
1
Main> fac 1
1
Main> fac 5
120
Main>

[Haskell] Exercise 4.1 - 4.4

○ Exercise 4.1
maxFourを3通りの方法で書く。maxは自分の書いた物を使う様に指定。

import Prelude hiding (max)
max :: Int -> Int -> Int
max x y
| x >= y = x
| otherwise = y

maxThree :: Int -> Int -> Int -> Int
maxThree x y z = max (max x y) z

maxFour1 :: Int -> Int -> Int -> Int -> Int
maxFour1 x y z w = max (max (max x y) z) w

maxFour2 :: Int -> Int -> Int -> Int -> Int
maxFour2 x y z w = max (max x y) (max z w)

maxFour3 :: Int -> Int -> Int -> Int -> Int
maxFour3 x y z w = max (maxThree x y z) w

○ Exercise 4.2
betweenは昇順でも降順でもTrueになる様に作る。

between :: Int -> Int -> Int -> Bool
between x y z = (weakAscendingOrder x y z)
|| (weakAscendingOrder z y x)

weakAscendingOrder :: Int -> Int -> Int -> Bool
weakAscendingOrder x y z = (x<=y) && (y<=z)

Main> between 1 2 3
True
Main> between 3 2 1
True
Main> between 3 2 2
True
Main> between 3 1 2
False
Main> between 1 3 2
False
Main> between 1 3 3
True
Main>

○ Exercise 4.3
指示通り Exercise 3.7, 3.8 で作った関数を再利用する。

-- from Ex3.8
threeEquals :: Int -> Int -> Int -> Bool
threeEquals m n p = (m==n) && (n==p)

-- from Ex3.7
threeDifferent :: Int -> Int -> Int -> Bool
threeDifferent m n p = (m /= n) && (n /= p) && (p /= m)

howManyEquals :: Int -> Int -> Int -> Int
howManyEquals x y z
| threeEquals x y z = 3
| threeDifferent x y z = 0
| otherwise = 2

Main> howManyEquals 34 25 36
0
Main> howManyEquals 34 25 34
2
Main> howManyEquals 34 34 34
3
Main>

○ Exercise 4.4
とりあえずの回答。

howManyOfFourEqual :: Int -> Int -> Int -> Int -> Int
howManyOfFourEqual x y z w
| (x == y) && (y == z) && (z == w) = 4
| threeEquals x y z = 3
| threeEquals y z w = 3
| threeEquals z x y = 3
| threeEquals w x y = 3
| x == y = 2
| x == z = 2
| x == w = 2
| y == z = 2
| y == w = 2
| z == w = 2
| otherwise = 0

Main> howManyOfFourEqual 1 2 3 4
0
Main> howManyOfFourEqual 1 2 3 3
2
Main> howManyOfFourEqual 1 3 3 3
3
Main> howManyOfFourEqual 3 3 3 3
4
Main> howManyOfFourEqual 1 3 3 1
2
Main>

別解:比較の6個の組み合わせの等号の数を数える。(Haskellっぽくない?)

isEq :: Int -> Int -> Int
isEq x y
| x == y = 1
| otherwise = 0

howManyOf4Equal :: Int -> Int -> Int -> Int -> Int
howManyOf4Equal x y z w =
case (count x y z w) of
6 -> 4 -- all combination ==
3 -> 3 -- x == y == z == x
2 -> 2 -- example x == y /= z == w
1 -> 2 -- example x == y only
0 -> 0
where count x y z w = (isEq x y) + (isEq x z) + (isEq x w)
+ (isEq y z) + (isEq y w) + (isEq z w)

Main> howManyOf4Equal 1 2 3 4
0
Main> howManyOf4Equal 1 2 3 3
2
Main> howManyOf4Equal 1 3 3 3
3
Main> howManyOf4Equal 3 3 3 3
4
Main> howManyOf4Equal 1 3 3 1
2
Main>