2008年7月10日木曜日

Haskell で簡単なテキスト処理 - データの抽出

Python で簡単なテキスト処理 (2) - データの抽出 で行なった処理と同じものを Haskell でも書いてみる。前提となる知識は、

ただし、今回、正規表現は使わなかった。 (cf. Haskell で正規表現)

 

コードを以下に示す。

import qualified System.IO.UTF8 as U

main = do cs <- U.getContents
          U.putStrLn $ unlines $ map extract $ filter match $ lines cs

match :: String -> Bool
match line = if (length $ words line) > 1 then True else False

extract :: String -> String
extract line = ds !! 0 ++ "\t" ++ ds !! 5
    where
      ds = words line

慣れていないため手間取った。 ^^; どうしても Ruby や Python のように、ファイルから一行ずつ読み込み、それが対象の行か判断して、必要な部分を順次抽出するのを繰り返すというイメージから抜けだせなかった。 (@_@;)

上記の場合、 最初  lines によって行ごとの文字列のリストに変換し、それに対して filter 関数で必要な行だけを抽出し、更にそれに対して map 関数で必要なデータ列のみに変換する。そして、最後、全体を文字列として出力するため unlines で元の文字列に戻す。

頭のなかのイメージとしては、いつでもリスト全体に対して関数を適用した結果どうなり、それに対し更に関数を適用したらどうなるかというイメージをした。それに加え、適用する関数が全体から見て、どういったレベルを対象としているのか考える。例えば、 map, filter であれば、リストの各要素が対象となり、この場合なら、行ごとの処理を意味する。また、それらの関数に渡す関数は要素に対する処理、つまり、行を対象とすることになる。そして、それらの処理を連結していく。これが最初なかなかイメージしずらかった。 ^^;

 

考え直し

上記を書いた後、釈然としなかったので、もう一度考えなおすことに。何がしっくりこなかったかと言えば、先ほどのコードを書いているとき、思考の過程がボトムアップだったように思える。どういうことかと言えば、「今データがこうなっており、関数を適用するとそれがああなり、それに対してこうすればいいのか」というように、順を追ってデータの処理を考えたという感じ。今度は処理の内容を順次考えるのではなく、トップダウン的に考えることにする。つまり、「こうなって欲しい」、そして、こうなるためには「ああなって欲しい」というに。また、「こうなるためには、こういった型の値を渡すと、こういう型が返ってきて...」というに考えてみる。具体的には、最初に関数名を書き、次にそれがどんな引数を取り、そしてどんな型を返さなくてはいけないか型宣言を考えてから、実装に移る。

そうすると、さっきよりも長くなってしまったが、書きやすかったように思えるが気のせいだろうか。 (?_?)

import qualified System.IO.UTF8 as U

main = do cs <- U.getContents
          U.putStrLn $ extract cs

extract :: String -> String
extract cs = unlines $ extractCol $ extractRow $ lines cs

extractRow :: [String] -> [String]
extractRow line = filter match line
    where
      match line = if (length $ words line) > 1 then True else False

extractCol :: [String] -> [String]
extractCol css  = map extractData css
    where
      extractData line = ds !! 0 ++ "\t" ++ ds !! 5
          where
            ds = words line

どういう風に考えていったかと言うと、まず getContents によって得た全体を extract 関数 で抽出して、putStrLn で出力すると考える。そうすると、extract 関数は String を引数に取り、 putStrLn が出力できるように String を返さなくてはいけない。ここではじめて lines – unlines のコンビネーションが必要で、その中では、行の抽出 (extractRow) と列の抽出 (extractCol) が必要であることがわかる。そして、extractRow, extractCol は文字列のリストを渡すと、文字列のリストを返すものだと想定する。そして、それぞれ実装がどうなるかを考えていくという思考の流れ。

うーん、こういう感じでいいのかなぁ~ (@_@;)


関連記事