トップ «前の日(11-28) 最新 次の日(11-30)» 追記   RSS 1.0 FEED  

Journal InTime


2002-11-29 (Fri)

_ 竜王戦

本局は阿部の方がよさそうに見える。(46手目時点)

がんばれー。

_ Demi

昔Javaで実装したスクリプト言語をふと思い出したが、探しても見つからない。 と思ったらなぜかまつもとさんが持っていたのでメールしてもらった。

今のJavaじゃ動かないだろうと思ったが、ちゃんと動いた。 すごいな、Java。

記念に置いときます。

<URL:/archive/demi/demi-0_1_6.tar.gz>

_ Webrickとcontinuation

まつもとさんの話だとWebrickとcontinuationを作ったWebアプリケーションの話が Ruby Conferenceであったらしい。

Rubyのcontinuationはスレッドをまたぐことができないのでどうしてるのかなと思った ら、WebrickではThreadを使ってなさげだとのこと。 ということはWebrickは同時に一つのリクエストしか処理できない?

ほんとかなあ。


2003-11-29 (Sat)

_ 弟来襲

弟が遊びに来たので、ちょっとした観光に出かけた。

やっぱり子供が二人になると大変だなあ。 上の子が一番楽しんでたかもしれない。


2011-11-29 (Tue)

_ RubyとHaskellによる簡単なCSVファイルの集計

簡単なCSVファイルの集計をHaskellでやろうと思ったら結構大変だったのでメモ。 IO返す関数をはじめて書いた気がする。

問題

以下のような形式の複数のCSVファイルをコマンドライン引数で受け取り、Applicant Nameごとに得点を集計して、合計得点でソートした結果を出力する。

Applicant Name,Project Title,Productivity and performance,Originality and creativity,Feasibility,Jadges point,Comment
Shugo Maeda,Introducing list comprehensions into Ruby,5,1,7,3,面白いけど必要ないと思います。
...

Ruby版 (30行)

Haskellで書こうと思ったけど手が出なかったので、とりあえずRubyで書いた。 色々やり方はあると思うけど、なるべく副作用を使わないやり方で。 flat_map便利。

class Assessment < Struct.new(:applicant, :title, :productivity, :originality, :feasibility, :judges_point)
  def +(other)
    self.class.new(self.applicant, self.title,
                   self.productivity + other.productivity,
                   self.originality + other.originality,
                   self.feasibility + other.feasibility,
                   self.judges_point + other.judges_point)
  end

  def total
    productivity + originality + feasibility + judges_point
  end

  def to_s
    [*to_a, total].join(",")
  end
end

assessments = ARGV.flat_map { |path|
  File.read(path).lines.reject { |line|
    /^Applicant Name/.match(line)
  }.map { |line|
    fields = *line.split(/,/)
    Assessment.new(*fields[0, 2], *fields[2, 4].map(&:to_i))
  }
}.group_by { |a| a.applicant }.flat_map { |k, as|
  as.inject(&:+)
}
puts "Applicant Name,Project Title,Productivity,Originality,Feasibility,Judges Point,Total"
puts assessments.sort_by { |i| -i.total }

Haskell版 (96行)

Ruby版を参考に悩みながら書いたのが以下。

import List
import Data.Ord
import IO
import System.Environment
import Text.ParserCombinators.Parsec

data Assessment = Assessment {
  applicant:: String,
  title :: String,
  productivity :: Int,
  originality :: Int,
  feasibility :: Int,
  judgesPoint :: Int
}

instance Show Assessment where
  show a = case a of
    (Assessment applicant title
     productivity originality feasibility judgesPoint)
     -> applicant ++ "," ++ title ++ ","
        ++ show productivity ++ "," ++ show originality ++ ","
        ++ show feasibility ++ "," ++ show judgesPoint ++ ","
        ++ show (totalPoint a)

totalPoint :: Assessment -> Int
totalPoint (Assessment applicant title
            productivity originality feasibility judgesPoint)
  = productivity + originality + feasibility + judgesPoint

aplus :: Assessment -> Assessment -> Assessment
(Assessment a1 t1 p1 o1 f1 j1) `aplus` (Assessment a2 t2 p2 o2 f2 j2)
  = Assessment a1 t1 (p1 + p2) (o1 + o2) (f1 + f2) (j1 + j2)

text :: Parser String
text = many $ noneOf ","

point :: Parser Int
point = do s <- char '-'
           return 0
    <|> do s <- many digit
           return $ read s

record :: Parser Assessment
record = do a <- text
            char ','
            t <- text
            char ','
            p <- point
            char ','
            o <- point
            char ','
            f <- point
            char ','
            j <- point
            return $ Assessment a t p o f j

parseLine :: String -> [Assessment]
parseLine line
  = case (parse record "" line) of
      Left err -> []
      Right a -> [a]

linesToAssessments :: [String] -> [Assessment]
linesToAssessments = concatMap parseLine

rejectHeaders :: [String] -> [String]
rejectHeaders = filter $ (not) . isPrefixOf "Applicant Name"

sortByApplicant :: [Assessment] -> [Assessment]
sortByApplicant = sortBy $ comparing applicant

groupByApplicant :: [Assessment] -> [[Assessment]]
groupByApplicant = groupBy $ \x y -> applicant x == applicant y

sumAssessments :: [[Assessment]] -> [Assessment]
sumAssessments = map $ foldl1 aplus

sortByTotalPoint :: [Assessment] -> [Assessment]
sortByTotalPoint = sortBy $ comparing (negate . totalPoint)

assessmentSummary :: [String] -> [Assessment]
assessmentSummary = sortByTotalPoint . sumAssessments
                    . groupByApplicant . sortByApplicant
                    . linesToAssessments . rejectHeaders

readLines :: FilePath -> IO [String]
readLines path = do h <- openFile path ReadMode
                    cs <- hGetContents h
                    return $ lines cs

main = do paths <- getArgs
          liness <- sequence (map readLines paths)
          let lines = concat liness
          let summary = assessmentSummary lines
          putStrLn "Applicant Name,Project Title,Productivity,Originality,Feasibility,Judges Point,Total"
          sequence_ (map print summary)

色々ハマったけど、以下のことを学んだ。

  • Parsecで/[^,]*/相当のことはmany (noneOf ",")と書ける。
  • sequenceでIOのリストからアクションの実行結果のリストを得られる。結果が要らない場合はsequence_でOK。
  • sortByはRubyのsort_byではなくsort {|x,y|...}相当。Data.Ord.comparingを使うとsort_byっぽく書ける。

残った疑問。

  • comparingの==版はないのか? equalingみたいな。
  • Rubyのsort_byみたいにSchwartzian Transformをやってくれる関数はないのだろうか。Haskellだと比較関数が速いからいらない?
  • getArgsからlinesを得るまでの流れをもっとスマートに書けないだろうか。
  • ファイルのクローズはどのタイミングでやればいいの?
  • というか、そもそもHaskellでこういうコードを普通はどう書くの?
Tags: Ruby Haskell
本日のツッコミ(全7件) [ツッコミを入れる]

_ ujihisa [ liness <- sequence (map readLines paths) ..]

_ ujihisa [ sequence_ (map print summary) は mapM_ print summ..]

_ akr [CSV などの表をいじり回すコマンドを以前から作っていたりするのですが、 それを使うとこんなですかねぇ。 % ta..]

_ shugo [> ujihisaさん なるほど、fmapとかmapMを使うとすっきりしますね。たしかにたくさんモジュールをimp..]

_ ikegami__ [比較以外の部分を僕なりに書いてみました。質問は Twitter でいつでも聞いてください。 https://gist..]

_ ikegami__ [「Equaling」は (==) : Eq a => a -> a -> Bool のことかなあ、と考えました。co..]

_ shugo [ありがとうございます。 僕のHaskell力ではまだ読めませんが、すっきりしたコードですね。 Showはたぶんin..]


2019-11-29 (Fri)

_ スプロケ・チェーン交換

photo

注文していたスプロケとチェーンがやっと届いたので交換してもらってきた。

スプロケはISA、チェーンはEKの520SRX2。

photo

午前中はこんなにいい天気だったのに夕方から雨が降ってきて帰りは辛かった。

走行距離: 15,925km

Tags: 250DUKE