我试图编写代码来在Haskell中执行以下简单任务:使用此字典查找单词的词源,存储为大型tsv文件(http://www1.icsi.berkeley.edu/~demelo/etymwn/)。我想我会将tsv文件解析(使用attoparsec)到Map中,然后我可以根据需要使用它来高效地查找词源(并做其他一些事情)。
这是我的代码:
public static void main(String[] args) {
Function f = new Function<Tuple2<String,Integer>,Integer>(){
@Override
public Integer call(Tuple2<String,Integer> paramT1) throws Exception {
return paramT1._2;
}
};
JavaStreamingContext ssc = JavaStreamingFactory.getInstance();
JavaReceiverInputDStream<String> lines = ssc.socketTextStream("localhost", 9999);
JavaDStream<String> words = lines.flatMap(s->{return Arrays.asList(s.split(" "));});
JavaPairDStream<String,Integer> pairRDD = words.mapToPair(x->new Tuple2<String,Integer>(x,1));
JavaPairDStream<String,Integer> aggregate = pairRDD.reduceByKey((x,y)->x+y);
JavaDStream<Integer> con = aggregate.transform(
(Function<JavaPairRDD<String,Integer>, JavaRDD<Integer>>)pRDD-> pRDD.map(
(Function<Tuple2<String,Integer>,Integer>)t->t._2));
//JavaDStream<Integer> con = aggregate.transform(pRDD-> pRDD.map(f)); It works
con.print();
ssc.start();
ssc.awaitTermination();
}
它适用于少量输入,但很快就会变得太低效。我不太清楚问题出在哪里,并且很快意识到即使是在我尝试时查看文件的最后一个字符这样的琐碎任务花了太长时间,例如与
{-# LANGUAGE OverloadedStrings #-}
import Control.Arrow
import qualified Data.Map as M
import Control.Applicative
import qualified Data.Text as DT
import qualified Data.Text.Lazy.IO as DTLIO
import qualified Data.Text.Lazy as DTL
import qualified Data.Attoparsec.Text.Lazy as ATL
import Data.Monoid
text = do
x <- DTLIO.readFile "../../../../etymwn.tsv"
return $ DTL.take 10000 x
--parsers
wordpair = do
x <- ATL.takeTill (== ':')
ATL.char ':' *> (ATL.many' $ ATL.char ' ')
y <- ATL.takeTill (\x -> x `elem` ['\t','\n'])
ATL.char '\n' <|> ATL.char '\t'
return (x,y)
--line of file
line = do
a <- (ATL.count 3 wordpair)
case (rel (a !! 2)) of
True -> return . (\[a,b,c] -> [(a,c)]) $ a
False -> return . (\[a,b,c] -> [(c,a)]) $ a
where rel x = if x == ("rel","etymological_origin_of") then False else True
tsv = do
x <- ATL.many1 line
return $ fmap M.fromList x
main = (putStrLn . show . ATL.parse tsv) =<< text
所以我的问题是:在方法和执行方面,我做错的主要事情是什么?有关更多Haskelly /更好代码的任何提示吗?
谢谢,
流便
答案 0 :(得分:5)
请注意,您要加载的文件有600万行和 您感兴趣的文本包含约。 120 MB。
为了建立一些下界,我首先创建了另一个包含的.tsv文件 etymwn.tsv文件的预处理内容。然后我计时了 花了这个perl程序读取该文件:
my %H;
while (<>) {
chomp;
my ($a,$b) = split("\t", $_, 2);
$H{$a} = $b;
}
这花了大约。 17秒,所以我希望任何Haskell程序 把握那个时间。
如果此启动时间不可接受,请考虑以下选项:
选项1在Chris Done的博客文章中讨论:
选项2和3将要求您使用IO monad。
首先,检查tsv
功能的类型:
tsv :: Data.Attoparsec.Internal.Types.Parser
DT.Text [M.Map (DT.Text, DT.Text) (DT.Text, DT.Text)]
您正在返回地图列表,而不仅仅是一张地图。这看起来不像 右。
其次,正如@chi建议的那样,我怀疑使用attoparsec
是懒惰的。
在某种程度上,它必须验证整个解析是否成功,
所以我看不出它是如何不能避免创建所有解析的行
在回来之前。
要真实地解析输入,请采用以下方法:
toPair :: DT.Text -> (Key, Value)
toPair input = ...
main = do
all_lines <- fmap DTL.lines $ DTLIO.getContent
let m = M.fromList $ map toPair all_lines
print $ M.lookup "foobar" m
您仍然可以使用attoparsec
来实施toPair
,但您将使用它
逐行而不是整个输入。
根据我的经验,使用ByteStrings比使用Text更快。
ByteStrings的这个toPair
版本比对应版本快4倍
文字版本:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.Attoparsec.ByteString.Lazy as AL
toPair :: L.ByteString -> (L.ByteString, L.ByteString)
toPair bs =
case AL.maybeResult (AL.parse parseLine bs) of
Nothing -> error "bad line"
Just (a,b) -> (a,b)
where parseLine = do
A.skipWhile (/= ' ')
A.skipWhile (== ' ')
a <- A.takeWhile (/= '\t')
A.skipWhile (== '\t')
rel <- A.takeWhile (/= '\t')
A.skipWhile (== '\t')
A.skipWhile (/= ' ')
A.skipWhile (== ' ')
c <- A.takeWhile (const True)
if rel == "rel:etymological_origin_of"
then return (c,a)
else return (a,c)
或者,只使用普通的ByteString函数:
fields :: L.ByteString -> [L.ByteString]
fields = L.splitWith (== '\t')
snipSpace = L.ByteString -> L.ByteString
snipSpace = L.dropWhile (== ' ') . L.dropWhile (/=' ')
toPair'' bs =
let fs = fields bs
case fields line of
(x:y:z:_) -> let a = snipSpace x
c = snipSpace z
in
if y == "rel:etymological_origin_of"
then (c,a)
else (a,c)
_ -> error "bad line"
加载地图所花费的大部分时间都在解析这些行。 对于ByteStrings,这大约是14秒。加载所有600万行 与50秒。对于文本。
答案 1 :(得分:0)
要添加到this answer,我想要注意的是,attoparsec实际上非常支持“基于拉式”的增量解析。您可以使用方便的parseWith
函数直接使用它。为了获得更好的控制,您可以使用parse
和feed
手动提供解析器。如果您不想担心这些问题,您应该可以使用类似pipes-attoparsec
的内容,但我个人觉得管道有点难以理解。