paizaラーニングをHaskellで解く じゃんけんの手の出し方 (paizaランク A 相当)


paizaの練習問題がhaskellに対応していたので練習に問いてみました。

じゃんけんの手の出し方 (paizaランク A 相当)
https://paiza.jp/learning/janken

健忘録です。
☆☆☆でテスト通りました。

プログラムはgitのレポジトリに置いてあります。
https://github.com/mamepon2580/portfolio/tree/master/Haskell/janken

↓↓↓ここからが内容↓↓↓

main関数

入力された値を受け取り実行するmain関数を作る。

gcp はその他の関数、readXreadY はIO関数のセクションで解説する。

main = do
  x <- getLine
  y <- getLine
  putStrLn $ show $ gcp (readX x) (readY x) y

IO関数

入力された値を扱い易い形に整形する。

  • readX は受け取った「n1 n2」という数字、スペース、数字の文字列からスペースを取り除きn1の数字のみをInt型として整形し抽出する関数である。

  • readY はreadXと同じくn2を抽出する。

  • getX はInt型の要素が2つのリストが与えられたとき1つ目の値を返す関数である。

  • getY はgetXと同じく2つ目の値を返す。

readX :: String -> Int
readX x = getX (map (\x -> read x ::Int) (words x))

readY :: String -> Int
readY x = getY (map (\x -> read x ::Int) (words x))

getX :: [Int] -> Int
getX (x:y:[]) = x

getY :: [Int] -> Int
getY (x:y:[]) = y

その他の関数

ここからがプログラムの内容

組み合わせ

まずグー、チョキ、パーのすべての組み合わせをベクトル化しリストにするために、パーの取りうる最大値を調べ、その値を軸にしてパーの数を減らしていきそのときのグーとチョキの数を調べる。

  • div は割り算の商を求める関数である。

  • nmOfPar は全体の指の本数を5で割りパーの取りうる最大の数を求め、その値からiを引いた値を返す関数である。

  • nmOfCho は全体の指の本数からパーの指の本数を引き、残りの指の本数からチョキの数を決定しその値を返す関数である。

  • nmOfGoo は全体の試合数からグーとパーの数を引き、グーの数を決定しその値を返す関数である。

nmOfPar :: Int -> Int -> Int
nmOfPar i x = (x `div` 5) - i

nmOfCho :: Int -> Int -> Int
nmOfCho i x = (x - (5 * (nmOfPar i x))) `div` 2

nmOfGoo :: Int -> Int -> Int -> Int
nmOfGoo i x y = x - (nmOfPar i y) - (nmOfCho i y)

ベクトル化

ここでは指の数がパーで出した指の数の合計とチョキで出した合計の和と一致しているか、またグーの数が0以下になっていないかを調べ、それを満たさない場合には空のリストを生成する。iの値を動かしながらすべてのパターンのグー、チョキ、パーの組み合わせをベクトル化する。

  • checkNmOfFinger は指の数がパーで出した指の数の合計とチョキで出した合計の和と一致しているか調べ、一致している場合にはtrueを、そうでない場合にはfalseを返す関数である。

  • ptnSearch はパーの取りうる最大の数からiだけ減らしたとき、指の数がパーで出した指の数の合計とチョキで出した合計の和と一致しており、またグーの数が0以下になっていないなら、そのときのグー、チョキ、パーの数の三組のベクトルの単一要素のリストを生成する。そうでない場合には、空のリストを生成し値を返す関数である。

  • ptnSearchCycle はiを動かし、パーで減らしたの指の数が全体の指の数を超えた場合には空のリストを生成し、そうでない場合にはそのiの数に対するベクトルを生成したあと、再帰的定義によりiにi - 1を代入した自身の関数を呼び出し、条件を満たすすべてのベクトルのリストを生成する関数である。

checkNmOfFinger :: Int -> Int -> Bool
checkNmOfFinger i x = (x - (5 * (nmOfPar i x)) - (2 * (nmOfCho i x))) == 0

ptnSearch :: Int -> Int -> Int -> [(Int,Int,Int)]
ptnSearch i x y | checkNmOfFinger i y && nmOfGoo >= 0 = [((nmOfGoo i x y) , (nmOfCho i y) , (nmOfPar i y))]
                | otherwise                           = []

ptnSearchCycle :: Int -> Int -> Int -> [(Int,Int,Int)]
ptnSearchCycle i x y | (5 * i) > y  = []
                     | (5 * i) <= y = (ptnSearch i x y) ++ ptnSearchCycle (i + 1) x y

勝利数の最大値

最後に相手の出すグー、チョキ、パーの文字列からグーを出した回数、パーを出した回数、チョキを出した回数の三組のベクトルを生成し、先程のベクトルのリストと比較し、勝つ数の最大値を求める。

  • check はG、C、Pからなる文字列から、グーなら(1,0,0)を、チョキなら(0,1,0)を、パーなら(0,0,1)を生成し、そのベクトルの和を求めその値を返す関数である。

  • vectorPuls は三組の2つのベクトルの和を定義している。

  • vectorMinus はグーで勝つ数、チョキで勝つ数、パーで勝つ数の三組のベクトルを返す関数である。

  • win は2つのベクトルの第一要素と第三要素、第二要素と第一要素、第三要素と第二要素を比較し、小さい方の値で生成した新しい三組のベクトルを返す関数である。

  • vectorPuls2 はベクトルの第一要素、第二要素、第三要素の和を返す関数である。

  • gcp はじゃんけんの出し方のベクトルのリストのそれぞれの要素に対し、vectorMinusを適応し勝つ数を求め、その最大値を求める関数である。

check :: String -> (Int,Int,Int)
check []                = (0,0,0)
check (x:xs) | x == 'G' = (1,0,0) `vectorPuls` check xs
             | x == 'C' = (0,1,0) `vectorPuls` check xs
             | x == 'P' = (0,0,1) `vectorPuls` check xs

vectorPuls :: (Int,Int,Int) -> (Int,Int,Int) -> (Int,Int,Int)
vectorPuls (x,y,z) (a,b,c) = (x + a,y + b,z + c)

vectorMinus :: (Int,Int,Int) -> (Int,Int,Int) -> (Int,Int,Int)
vectorMinus (x,y,z) (a,b,c) = (x `win` c,y `win` a,z `win` b)

win :: Int -> Int -> Int
win x y | x <= y = x
        | x >= y = y

vectorPuls2 :: (Int,Int,Int) -> Int
vectorPuls2 (x,y,z) = x + y + z

gcp :: Int -> Int -> String -> Int
gcp x y z = maximum (map vectorPuls2 (map  (\x -> vectorMinus (check z) x) (ptnSearchCycle 0 x y)))

初投稿キメたぜ。