Ruby で関数型プログラミングっぽく(コピペ) + Haskell 版
parrot.hatenadiary.jpここのブログ記事を読んで感銘を受けました。だからこれを読んでもらえればよいのですが、せっかくなのでコピペしておきます。元記事に感謝です。
まずは問題。
ある数字にそれを逆に並べた数字を足すという計算を、
https://parrot.hatenadiary.jp/entry/20110302/1299051431
回文数(上から読んでも下から読んでも同じ数)になるまで繰り返すとき、
もっとも計算回数を要する二桁の数を答えなさい
例:ab+ba=123の場合、123+321=444で回文数なので、2回となる
Ruby コードです。多少自己流に書き直しました。
reverse_num = ->(num) {num.to_s.reverse.to_i} is_palindrome = ->(num) {num == reverse_num.(num)} execute = ->(count, num) { r = num + reverse_num.(num) is_palindrome.(r) ? count : execute.(count + 1, r) } result = (10..99).map{|n| [n, execute.(1, n)]} max_count = result.map(&:last).max result.select{|r| r.last == max_count}.each{|r| puts "num:#{r.first} count:#{r.last}"}
結果。89 と 98 ですね。24回繰り返しています。
$ time ruby palindrome_num.rb num:89 count:24 num:98 count:24 real 0m0.091s user 0m0.076s sys 0m0.012s
いや、すばらしい。
Haskell 版
頑張って Scala ならぬ Haskell で同様のことをやってみました。
palindrome_num.hs
reverseNum :: Int -> Int reverseNum num = read $ reverse $ show num isPalindrome :: Int -> Bool isPalindrome num = (num == reverseNum num) execute :: Int -> Int -> Int execute count num = if isPalindrome r then count else execute (count + 1) r where r = num + reverseNum num main :: IO () main = putStr $ unlines $ map toS $ [r | r <- result, last r == maxCount] where result = [[n, execute 1 n] | n <- [10..99]] maxCount = maximum $ map last result toS [a, b] = "num:" ++ show a ++ " count:" ++ show b
こんなのでいいのかな。
結果。
$ time ./palindrome_num num:89 count:24 num:98 count:24 real 0m0.003s user 0m0.000s sys 0m0.000s
当然のことながら瞬殺ですな。
しかし、Ruby でも結構関数型っぽく書けますね。ほとんどそのまま Haskell になるじゃん。自分は Ruby で慣れているので、Ruby の方が見やすいくらいだ。以下の記事もよろしければどうぞ。
obelisk.hatenablog.com
- 作者:Miran Lipovača
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
追記(2020/12/4)
Ruby 版後半があまり美しくないので、少し凝ってみる。
module Enumerable def max_select pool = [] max_num = -Float::INFINITY each do |i| n = yield(i) if n > max_num max_num = n pool = [i] elsif n == max_num pool << i end end [max_num, pool] end end
Enumerable#max_select
は、ブロックの返り値の最大値(max_num
)を求めて、そのような最大値になるようなものをレシーバーからコレクトし(pool
)、[max_num, pool]
を返すメソッド。例えばこんな風に使える。ローマ字化した県名のうち、もっとも文字数が長いもの。
url = "https://gist.githubusercontent.com/koseki/38926/raw/671d5279db1e5cb2c137465e22424c6ba27f4524/todouhuken.txt" prefectures = URI.open(url).each_line.map {|l| l.chomp.split.last} prefectures.max_select(&:size) #=>[9, ["fukushima", "yamanashi", "hiroshima", "yamaguchi", "tokushima", "kagoshima"]]
9文字が最大値だとわかる。
まあ、他に使いみちがあるかどうかわからないものだが(笑)、これを使って以下のように。
reverse_num = ->(num) {num.to_s.reverse.to_i} is_palindrome = ->(num) {num == reverse_num.(num)} execute = ->(num, count = 1) { r = num + reverse_num.(num) is_palindrome.(r) ? count : execute.(r, count + 1) } count, numbers = (10..99).max_select(&execute) puts "max count: #{count}" puts "number: #{numbers}"
ちょっと関数型プログラミングっぽいかも知れない笑。