Skip to content

Instantly share code, notes, and snippets.

@nisshiee
Created February 19, 2012 11:35
Show Gist options
  • Save nisshiee/1863320 to your computer and use it in GitHub Desktop.
Save nisshiee/1863320 to your computer and use it in GitHub Desktop.
ScalazのIterateeでファイルを読み込むTipsを埋め込んだ
package iteratee.practice
import scalaz._
import Scalaz._
import IterV._
import effects._
import java.io.File
/**
* scalazのIterateeを使ってファイルを読み込む練習
* http://code.google.com/codejam/contest/889487/dashboard
* <pre>
* M C W
* A1 B1
* ...
* AC BC
* </pre>
*
* @author nishioka
*
*/
object Practice3 extends App {
/** 読み込みたい型 */
case class Cut(a: Int, b: Int)
case class Problem(m: Int, c: Int, w: Int, cuts: List[Cut])
/** parseMcw, parseCutを作るための布石 */
def line2strlist: String => List[String] = _.split(" ").toList
/* parseIntの失敗を握りつぶすのはホントはよくないけど、今回メインじゃないので割愛 */
def strlist2intlist: List[String] => List[Int] = l =>
(l map (_.parseInt.toOption)).flatten
def line2intlist: String => List[Int] = line2strlist >>> strlist2intlist
def intlist2mcw: List[Int] => Option[(Int, Int, Int)] = {
case m :: c :: w :: Nil => (m, c, w).some
case _ => None
}
def intlist2cut: List[Int] => Option[Cut] = {
case a :: b :: Nil => Cut(a, b).some
case _ => None
}
/** 文字列からの型変換(この時点ではIOとかIterateeとか無縁) */
def parseMcw = line2intlist >>> intlist2mcw
def parseCut = line2intlist >>> intlist2cut
/* ----- Iterateeチックなのはここから ----- */
/** MCWを読み込むIterV */
def readMcw = head[String] map (_ >>= parseMcw)
/** pureするのに便利なのでIterVのエイリアス作っとく */
type IM[A] = IterV[String, A]
/**
* n個のCutをOption[List[Cut]]として読み込むIterV
*
* IterV[E, Option[A]]をn(有限)個つなげてIterV[E, List[Option[A]]]を作るには、
* foldRightでListにアキュムレートしていくのが良さそう
* (ホントはsequence[IM, Option[A]]できれば一番かっこいいのになぁ)
*
* 最後にList[Option[A]]にsequence[Option, A]を適用して、
* Option[List[A]]として返す
* flattenはしないことで、「失敗するかもしれない文脈」を引き継ぐ
*/
def readCut(n: Int) = {
val listIterV = (head[String] map (_ >>= parseCut)).replicate[List](n)
listIterV.foldRight(List[Option[Cut]]().pure[IM])((i, acc) => for {
cut <- i
rest <- acc
} yield cut :: rest) map (_.sequence[Option, Cut])
}
/**
* ProblemをOption[Problem]として読み込むIterV
*
* readMcwとreadCutの組合せだが、mcwのcの値をreadCutに引き渡す必要があるので、
* for式で書いた方が綺麗
*
* また、Optionによる「失敗するかもしれない文脈」には気を遣い続ける
*/
def readProb = for {
mcw <- readMcw
cut <- mcw match {
case Some(_@ (_, c, _)) => readCut(c)
case _ => none[List[Cut]].pure[IM]
}
} yield (mcw, cut) match {
case (Some(_@ (m, c, w)), Some(cut)) => Problem(m, c, w, cut).some
case _ => none[Problem]
}
/**
* ファイルを読み終わるまでreadProbを繰り返す
*
* 最後にsequenceを使ってList[Option[Problem]]をOption[List[Problem]]に直す
* これもflattenを使わずに、「失敗するかもしれない文脈」を引き継ぐ
*/
def readRepeat = repeat[String, Option[Problem], List](readProb) map (_.sequence[Option, Problem])
/**
* 1行目を捨てる処理を加え、これで入力全体を読み込むIterVの完成
*/
def read = drop[String](1) >>=| readRepeat
/* ----- IOが絡むのはここから ----- */
/** FileクラスはJavaのもの */
val f = new File("practice/3-1.in")
/** EnumeratorMはscalaz.effectsパッケージを使えば一発取得 */
val lines = getFileLines(f)
/**
* 作成したIterVを使ってファイルを読み込み、その結果を標準出力に出す
* 入力と出力を>>=で繋げて、入力から出力までをまとめて1つのIOにする
*
* また、ここまでずっと引き継いできた「失敗するかもしれない文脈」は
* ここが唯一の分岐
*/
val io = lines(read) >>= { iterv =>
iterv.run match {
case Some(l) => {
val listIO = l map (_.toString) map putStrLn
listIO.sequence[IO, Unit] map (_ => ())
}
case None => putStrLn("parse error")
}
}
/* 全てのIO処理はここに全て集約される */
io.unsafePerformIO
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment