読者です 読者をやめる 読者になる 読者になる

定食屋おろポン

おろしポン酢と青ネギはかけ放題です

シカクいアタマでハスケルする

中学受験予備校の日能研が昔から、「シカクいアタマをマルくする」という広告を張っている。電車内のドア横・窓上ポスターによく掲載されているため、ご存じの方も多いだろう。

2015年2月掲載分(とウェブサイトに記載されているが、1月時点で既に掲載されている。雑誌の発売月みたいなものだろうか)の問題がこちら。*1

2015年2月掲載 鴎友学園女子中学校【算数】 | シカクいアタマをマルくする。 | 中学受験-小学生のための中学受験塾。日能研

さすがに良問だ。 (2/29を含めて)366日分を全て書き出すと時間が掛かり過ぎるため、「理詰めで短時間で解ける方法があるはずだ」「どうやったら候補を絞り込めるだろうか」と考えさせる。 また、問1と問2でほどよい難易度の上昇がある。 (大人にとっても、電車内で暗算しようとするとかなり歯ごたえがあってよい)

中学受験生にとって良問であると同時に、なにやらプログラミングで解くにも適した問題に見える。

  • 日付ライブラリの基礎的な使用(366日の列挙)
  • 数字のゼロ埋め(1/111ではなく、0101として扱う必要がある)
  • リストの最大、最小、フィルタなどの各種操作

が適度な難易度(中級者以上には、かなり物足りないと言われるかもだけど)で散りばめられている。

Rubyで解いたら作業なので、Haskellで解いてみる。

import Data.Time (Day, fromGregorian, toGregorian)
import Data.List (minimumBy, maximumBy)
import Data.Ord (comparing)

char2segnum :: Char -> Int
char2segnum '0' = 6
char2segnum '1' = 2
char2segnum '2' = 5
char2segnum '3' = 5
char2segnum '4' = 4
char2segnum '5' = 5
char2segnum '6' = 6
char2segnum '7' = 4
char2segnum '8' = 7
char2segnum '9' = 6

countSegnum :: String -> Int
countSegnum = sum . map char2segnum

zeroPadding :: Int -> Int -> String
zeroPadding width = reverse . take width . (++ repeat '0') . reverse . show

allDaysInLeap :: [Day]
allDaysInLeap = [fromGregorian 2000 1 1..fromGregorian 2000 12 31]

gregorian2string :: Day -> String
gregorian2string date = let (_, m, d) = toGregorian date
                        in  zeroPadding 2 m ++ zeroPadding 2 d

day2segnum :: Day -> Int
day2segnum = countSegnum . gregorian2string

dayHasMinSegnum :: (Day, Int)
dayHasMinSegnum = minimumBy (comparing snd) $ zip allDaysInLeap (map day2segnum allDaysInLeap)

dayHasMaxSegnum :: (Day, Int)
dayHasMaxSegnum = maximumBy (comparing snd) $ zip allDaysInLeap (map day2segnum allDaysInLeap)

daysHasSegnum :: Int -> Int
daysHasSegnum n = length $ filter ((==n) . snd) $ zip allDaysInLeap (map day2segnum allDaysInLeap)

main :: IO ()
main = do
  putStrLn $ "1.1: " ++ show dayHasMinSegnum
  putStrLn $ "1.2: " ++ show dayHasMaxSegnum
  putStrLn $ "2: " ++ show (daysHasSegnum 24)

出力はこのとおり

1.1: (2000-11-11,8)
1.2: (2000-08-08,26)
2: 16

日付を扱うのがちょっと大変かと思ったが、実際に試してみると非常に楽で助かった。Day型がEnumインスタンスなので、1/1から12/31の列挙が[fromGregorian 2000 1 1..fromGregorian 2000 12 31]で済むのが素晴らしい。

Ruby(Date.new(2000,1,1)..Date.new(2000,12,31))と全然変わらない。

*1:そもそもウェブサイトに掲載されていることを初めて知った。これからは「シカクいアタマをマルくするが貼ってあるけど人が前に立ってて問題が読めない..死のうかな」といったときもHPを見れば大丈夫だ。