wkoikingのブログ

内容は主にhaskellやxyzzy関連です。

【Haskell】 インデントで表現された木構造のパース

今日はhaskellネタ。

現在私が務めている会社は、良くも悪くも日本の伝統的な製造業といった感じなので、 今のところ、haskellを製品のソフトウェア開発に使う機会は全くないのですが1、 excelデータ(csvとかtsv)の処理等で、haskellをスクリプト的にを使っています。

今回も普通のサラリーマンの日常業務をhaskellで効率化するのを念頭に、 記事を書いてみます。

普通のサラリーマンが出会うデータは大抵の場合excelデータだと思うので、 以下の様なモデルを考えています。

入力: xlsデータ

出力: xlsデータ

手順

  1. 直接コピペか「ファイルの種類の変更」でxlsをtsvデータに変換
  2. haskellで処理
    1. haskellでtsvデータをパース
    2. haskellでデータを処理
    3. tsvで出力
  3. excelに出力tsvをコピペ(おわり)

また、日常的に(excelで)扱うデータ形式は、大抵の場合、

の2種類だと思うので、今回は、 インデントで表現された木構造のパース方法について、書いていこうと思います2

インデントて表現された木構造のパース

indentsパッケージ利用します。

前置きが長くなりましたが、以下がソースコードです。

ソースコード

module IndentedTree where

import Control.Applicative ( (*>) )
import Data.Char (isSpace)
import Data.Tree (Tree (..))
import Text.Parsec (many1, letter, try, option, spaces, char, runParserT)
import Text.Parsec.Indent (withBlock, runIndent, IndentParser(..))
import MyParser (float) -- 浮動小数点をパースする適当なパーサー

type Parser a = IndentParser String () a

parseIndentedTree input = runIndent "" $ runParserT aTree () "" input

aTree :: Parser (Tree (String, Double))
aTree = spaces *> withBlock Node aNodeHeader aTree

aNodeHeader :: Parser (String, Double)
aNodeHeader = do
  str <- many1 letter
  i <- option 0 (char ':' *> float)
  spaces
  return (str, i)

-- 入力例
example = unlines [
    "foo:100",
    "    knf:2",
    "    inf:5",
    "    enx:15",
    "        indent",
    "        dog:4",
    "            bar:10",
    "    kobolt",
    "    wkoiking:6",
    "        taiwan",
    "    copilot:50"

実行結果

特に、説明をしなくても、Text.Parsec.Indentモジュールの使い方は明瞭だと思いますが、 一点だけ注意点があります:

withBlockコンビネータは現在のインデントの深さで、入れ子構造を判断しますが、 それ自身は入力列のスペースを消費しません。したがって、スペースを明示的に読み飛ばすのを 忘れないようにしなければなりません。

参考にしたサイト

おまけ

  • MyParser
  • ちゃんと前置きのtsv

    1. パース(parseIndentedTree)して、
    2. 適当な処理(bottomUp, topDown)をして、
    3. tsvで出力(showTree)

    するコード


  1. そもそも私はプログラマではありません。?

  2. 表形式のパースは、比較的簡単なので、省略します。?