17

私はスタック オーバーフロー データ ダンプを調査してきましたが、これまでのところ、使いやすい XML と正規表現による「解析」を利用しています。さまざまな Haskell XML ライブラリを使用して、特定のユーザーによるドキュメント順の最初の投稿を見つけようとした私の試みはすべて、厄介なスラッシングに遭遇しました。

タグスープ

import Control.Monad
import Text.HTML.TagSoup

userid = "83805"

main = do
  posts <- liftM parseTags (readFile "posts.xml")
  print $ head $ map (fromAttrib "Id") $
                 filter (~== ("<row OwnerUserId=" ++ userid ++ ">"))
                 posts

hxt

import Text.XML.HXT.Arrow
import Text.XML.HXT.XPath

userid = "83805"

main = do
  runX $ readDoc "posts.xml" >>> posts >>> arr head
  where
    readDoc = readDocument [ (a_tagsoup, v_1)
                           , (a_parse_xml, v_1)
                           , (a_remove_whitespace, v_1)
                           , (a_issue_warnings, v_0)
                           , (a_trace, v_1)
                           ]

posts :: ArrowXml a => a XmlTree String
posts = getXPathTrees byUserId >>>
        getAttrValue "Id"
  where byUserId = "/posts/row/@OwnerUserId='" ++ userid ++ "'"

xml

import Control.Monad
import Control.Monad.Error
import Control.Monad.Trans.Maybe
import Data.Either
import Data.Maybe
import Text.XML.Light

userid = "83805"

main = do
  [posts,votes] <- forM ["posts", "votes"] $
    liftM parseXML . readFile . (++ ".xml")
  let ps = elemNamed "posts" posts
  putStrLn $ maybe "<not present>" show
           $ filterElement (byUser userid) ps

elemNamed :: String -> [Content] -> Element
elemNamed name = head . filter ((==name).qName.elName) . onlyElems

byUser :: String -> Element -> Bool
byUser id e = maybe False (==id) (findAttr creator e)
  where creator = QName "OwnerUserId" Nothing Nothing

どこで私は間違えましたか?Haskell で大量の XML ドキュメントを処理する適切な方法は何ですか?

4

6 に答える 6

17

これらすべてのケースで String IO を実行していることに気付きました。大量のテキストを効率的に処理したい場合は、絶対に Data.Text または Data.Bytestring(.Lazy) を使用する必要があります。これは、非常に大きなフラット ファイルには不適切な表現である String == [Char] です。

これは、バイト文字列をサポートする Haskell XML ライブラリを使用する必要があることを意味します。数十の xml ライブラリがここにあります: http://hackage.haskell.org/packages/archive/pkg-list.html#cat:xml

どのサポートがバイト文字列をサポートしているかはわかりませんが、それがあなたが探している条件です。

于 2010-02-18T23:36:39.857 に答える
9

以下はhexpatを使用する例です:

{-# LANGUAGE PatternGuards #-}

module Main where

import Text.XML.Expat.SAX

import qualified Data.ByteString.Lazy as B

userid = "83805"

main :: IO ()
main = B.readFile "posts.xml" >>= print . earliest
  where earliest :: B.ByteString -> SAXEvent String String
        earliest = head . filter (ownedBy userid) . parse opts
        opts = ParserOptions Nothing Nothing

ownedBy :: String -> SAXEvent String String -> Bool
ownedBy uid (StartElement "row" as)
  | Just ouid <- lookup "OwnerUserId" as = ouid == uid
  | otherwise = False
ownedBy _ _ = False

の定義ownedByは少し不格好です。代わりにおそらくビューパターン:

{-# LANGUAGE ViewPatterns #-}

module Main where

import Text.XML.Expat.SAX

import qualified Data.ByteString.Lazy as B

userid = "83805"

main :: IO ()
main = B.readFile "posts.xml" >>= print . earliest
  where earliest :: B.ByteString -> SAXEvent String String
        earliest = head . filter (ownedBy userid) . parse opts
        opts = ParserOptions Nothing Nothing

ownedBy :: String -> SAXEvent String String -> Bool
ownedBy uid (ownerUserId -> Just ouid) = uid == ouid
ownedBy _ _ = False

ownerUserId :: SAXEvent String String -> Maybe String
ownerUserId (StartElement "row" as) = lookup "OwnerUserId" as
ownerUserId _ = Nothing
于 2010-02-22T20:02:33.627 に答える
8

私のfast-tagsoupライブラリを試すことができます。これは、 tagsoupの単純な置き換えであり、20 ~ 200MB/秒の速度で解析します。

tagsoup パッケージの問題は、Text または ByteString インターフェイスを使用しても、内部で String を使用して動作することです。fast-tagsoup は、高パフォーマンスの低レベル解析を使用して厳密な ByteString を処理しながら、遅延タグ リストを出力として返します。

于 2012-10-18T12:05:16.893 に答える
3

同様の問題が発生しました(HXTを使用)-HXTでExpatパーサーを使用することでメモリの問題を回避しました。5MBのXMLファイルでは、ドキュメントを読み取って印刷するだけです。ピークメモリ消費量は2ギガから約180MBになり、実行時間ははるかに短くなりました(測定されませんでした)。

于 2012-10-18T05:05:13.437 に答える
3

TagSoup は、そのクラス Text.StringLike を介して ByteString をサポートします。あなたの例に必要な唯一の変更は、 ByteString.Lazy's を呼び出し、 a をreadFileに追加することでしfromStringfromAttrib:

import Text.StringLike
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Char8 as BSC

userid = "83805"
file = "blah//posts.xml"
main = do
posts <- liftM parseTags (BSL.readFile file)
print $ head $ map (fromAttrib (fromString "Id")) $
               filter (~== ("<row OwnerUserId=" ++ userid ++ ">"))
               posts  

あなたの例は私のために実行され(4ギガRAM)、6分かかりました。ByteString バージョンには 10 分かかりました。

于 2010-02-28T17:17:39.963 に答える
1

おそらく、怠惰な XML パーサーが必要です。この使用法は、入力を非常に単純にスキャンするように見えます。HaXml には遅延パーサーがありますが、正しいモジュールをインポートして明示的に要求する必要があります。

于 2010-02-19T11:55:22.790 に答える