Hatena::Grouptopcoder

TopCoder戦記

研究開発者・ellerのTopCoder挑戦記録。言語は主にJavaを使用しています。ドキュメンテーションコメントはSubmit完了後、ブログ掲載前に補完したものです。

2011-04-17

コードリーディング

| 20:48

GCJは入力の1行目にデータ量、2行目移行に加工対象データが渡されるケースが多そう。こうした入力形式をどう扱えば良いのか、先人のコードを元に考えてみる。こうしてコードを公開してくださっていることに感謝。

<$>とは何か

他の箇所は大体理解できるものの、main関数の定義だけは見慣れない<$>演算子が使われていたためよくわからなかった。ひとつずつ分解してみる。

-- a part of submitted code for A
main = gcjMain solve $ map read . words <$> getLine

<$>演算子Control.Applicativeで定義されている、fmap関数へのシノニム。わかりやすさのため、fmapに置き換えると以下のようになる。

main = gcjMain solve $ fmap (map read . words) getLine

関数適用の優先順位はどんな演算子の優先順位よりも高いことから、"read . words"よりも"map read"の方が優先的に適用される。

main = gcjMain solve $ fmap ((map read) . words) getLine

$より右の式は「入力から得た文字列を空白で区切って[String]に変換し、その各要素を数値に変換することで[数値]を得る」という意味を持っているように見える。実際の型をghciで調べると以下のようにまだIOモナドの形を保っている=入力未実行なので、この解釈は厳密には誤りであろう。でも雰囲気的にはそんな感じ。「入力をよろしく解釈して[数値]を返すIOモナド」というのが正しいか。

*Main> :t fmap ((map read) . words) getLine
fmap ((map read) . words) getLine :: (Read a) => IO [a]

実際に使ってみる

練習用問題を解いてみる。

この問題ではStringを空白で区切って[String]とし、これをreverseすれば目的の出力が得られそう。空白で区切って逆転させるconvert関数をgetLineにfmapすることで、作用させるだけで出力すべき文字列が得られるIOモナドを作ってみる。

import Control.Monad
import Control.Applicative
import Data.List
import Text.Printf

convert :: String -> String
convert = concat . (intersperse " ") . reverse . words

gcjMain m = do
  n <- readLn
  dat <- replicateM n m
  forM_ (zip [1..n] dat) $ \(i, d) -> do
    printf "Case #%d: %s\n" i d

main = gcjMain $ convert <$> getLine

submitしたところcorrectだった。replicateMとforM_を組み合わせた繰り返しはCodeJamでのパターンになりそう。