or1ko's diary

日々を書きます

JSONの読み込み方法

言語処理100本ノック 2015

言語処理100本ノックの第3章に入るにあたり、JSON形式の読み取り方法について調べた。
標準のライブラリにはないため、別途パッケージが必要で、Googleで検索した限りでは、下記の2つの名前が上がった。

aeson: Fast JSON parsing and encoding | Hackage
json: Support for serialising Haskell to and from JSON | Hackage

この他にもHackageのJSONカテゴリにはいくつもあるが、とりあえず読み込めればよいため、この2つを試した。

要求としては、日本語(UTF8)が処理できて、宣言したdataに取り込めることの2点があるため、
下記の構造を持つJSONデータを読み込み、titleを出力するサンプルを作成することにした。

{
   "title" : "...",
   "text" : "..."
}
Aeson

まずは、Aesonでのサンプル。Aesonのバージョンは0.9.0.1。
下記のプログを参考に作成した。
Parsing JSON with Aeson - School of Haskell | FP Complete

{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}

import Data.Aeson as A
import Control.Applicative
import System.IO.UTF8 as I8
import System.Environment
import Data.Maybe
import Data.ByteString.Lazy.UTF8
import GHC.Generics

data Article = Article {
  text :: String,
  title :: String
} deriving (Eq, Show, Generic)
instance FromJSON Article

main = do
  filename <- head <$> getArgs
  body <- fromString <$> I8.readFile filename
  I8.writeFile "out2.txt" $ title $ fromJust $ A.decode body
json

次に、jsonのサンプルプログラム。バージョンは0.9.1
下記のブロクを参考に作成
Haskell で XML と JSON をパースする方法 - satosystemsの日記

{-# LANGUAGE DeriveDataTypeable #-}
import Text.JSON
import Text.JSON.Generic
import Control.Applicative
import System.IO.UTF8 as I8
import System.Environment

data Article = Article {
  text :: String,
  title :: String
} deriving (Eq, Show, Data, Typeable)

main = do
  filename <- head <$> getArgs
  body <- I8.readFile filename
  I8.writeFile "out.txt" $ text $ decodeJSON body 
まとめ

若干心配していた日本語の扱いもどちらも問題なく、この程度の利用では、利便性はあまり変わらなかった。

ひとまず、Aesonを使用していこうと思う。
調べた限りでは、Aesonのほうが検索のヒットが多く、
また、jsonよりもAesonを薦めている記事があったので。

19

言語処理100本ノック 2015

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.

19.hs

import System.Environment
import System.IO.UTF8 as I8
import qualified Data.Map as M
import Data.List 
import Data.Maybe

main = do
  filename <- getArgs >>= return . head
  body <- I8.readFile filename
  let cfs = M.fromList $ map (\x -> (head x, length x) ) $ group $ sort $ map (head . words) $ lines body
  I8.writeFile "19.out.txt" $ unlines $ sortBy (\x y -> compareByCf cfs (head $ words x) (head $ words y)) $ lines body

compareByCf cfs x y = fromMaybe EQ $ do
    xt <- M.lookup x cfs
    yt <- M.lookup y cfs
    return $ compare xt yt

実行結果が出現頻度順に並んでないと勘違いして、だいぶ悩んでしまった。
出現頻度順に並んでも、同じ単語が続けて現れるわけではないですね...。

18

言語処理100本ノック 2015

18. 各行を3コラム目の数値の降順にソート
各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).

> Get-Content -Encoding UTF8 .\hightemp.txt | ConvertFrom-Csv -Header a,b,c,d -Delimite
r `t | Sort-Object -Property c

18.hs

import System.Environment
import System.IO.UTF8 as I8
import Data.List 
import Data.Ord

main = do
  filename <- getArgs >>= return . head
  body <- I8.readFile filename
  I8.writeFile "18.out.txt" $ unlines $ nub . sortBy (\x y -> compare (f y) (f x)) $ lines body
  where
    f = flip (!!) 2 . words

17

言語処理100本ノック 2015

17. 1列目の文字列の異なり
1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはsort, uniqコマンドを用いよ.

> Get-Content -Encoding utf8 .\hightemp.txt | ConvertFrom-Csv -Header a -Delimiter `t |
 % { $_.a } | Sort-Object -Unique
愛知県
愛媛県
岐阜県
群馬県
高知県
埼玉県
山形県
山梨県
静岡県
千葉県
大阪府
和歌山県

17.hs

import System.Environment
import System.IO.UTF8 as I8
import Data.List 

main = do
  filename <- getArgs >>= return . head
  body <- I8.readFile filename
  I8.writeFile "17.out.txt" $ unlines $ nub . sort . map (head . words) $ lines body

shのsortを動かくのが面倒だったため、PowerShellで実施することにした。

16

言語処理100本ノック 2015

16. ファイルをN分割する
自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.

16.hs

import System.Environment
import Control.Monad

main = do
  args <- getArgs
  let num = (read :: String -> Int) $ head args
  let filename = args !! 1
  body <- getContents 
  let l = lines body
  let a = div (length l) num + mod (length l) num
  let b = splitAt' a l
  mapM_ (\x -> writeFile (filename ++ (show x) ++ ".txt") $ unlines (b !! (x - 1))) [1..num] 

splitAt' n xs 
  | length xs <= n = [xs]
  | otherwise = x :(splitAt' n y) 
    where
      (x, y) = splitAt n xs

結構、プログラムが長くて、読みにくくなってしまった。行をn個に分割する関数を定義したら、良かったかもしれない。

15

言語処理100本ノック 2015

15. 末尾のN行を出力
自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.

> tail -n 10

15.hs

import System.Environment

main = do
  num <- getArgs >>= return . (read :: String -> Int) . head 
  body <- getContents 
  putStrLn $ unlines $ reverse $ take num $ reverse $ lines body

14.hsを流用したが、
このプログラムだと、入力された文字列を瞬間的に全部保持する!?

14

言語処理100本ノック 2015

14. 先頭からN行を出力
自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.

>head -n 10 

14.hs

import System.Environment

main = do
  num <- getArgs >>= return . (read :: String -> Int) . head 
  body <- getContents 
  putStrLn $ unlines $ take num $ lines body