我试图使用Haskell Iteratee库提出相当于“wc -l”的内容。下面是“wc”的代码(它只计算单词 - 类似于hackage上的iteratee示例中的代码),并且运行速度非常快:
{-# LANGUAGE BangPatterns #-}
import Data.Iteratee as I
import Data.ListLike as LL
import Data.Iteratee.IO
import Data.ByteString
length1 :: (Monad m, Num a, LL.ListLike s el) => Iteratee s m a
length1 = liftI (step 0)
where
step !i (Chunk xs) = liftI (step $ i + fromIntegral (LL.length xs))
step !i stream = idone i stream
{-# INLINE length1 #-}
main = do
i' <- enumFile 1024 "/usr/share/dict/words" (length1 :: (Monad m) => Iteratee ByteString m Int)
result <- run i'
print result
{- Time measured on a linux x86 box:
$ time ./test ## above haskell compiled code
4950996
real 0m0.013s
user 0m0.004s
sys 0m0.007s
$ time wc -c /usr/share/dict/words
4950996 /usr/share/dict/words
real 0m0.003s
user 0m0.000s
sys 0m0.002s
-}
现在,您如何扩展它以计算快速运行的行数?我做了一个使用Prelude.filter的版本只过滤“\ n”到长度,但它比linux“wc -l”要慢,因为内存太多了,gc(懒惰评估,我猜)。所以,我使用Data.ListLike.filter编写了另一个版本,但它不会编译,因为它没有键入check - 这里的帮助将不胜感激:
{-# LANGUAGE BangPatterns #-}
import Data.Iteratee as I
import Data.ListLike as LL
import Data.Iteratee.IO
import Data.ByteString
import Data.Char
import Data.ByteString.Char8 (pack)
numlines :: (Monad m, Num a, LL.ListLike s el) => Iteratee s m a
numlines = liftI $ step 0
where
step !i (Chunk xs) = liftI (step $i + fromIntegral (LL.length $ LL.filter (\x -> x == Data.ByteString.Char8.pack "\n") xs))
step !i stream = idone i stream
{-# INLINE numlines #-}
main = do
i' <- enumFile 1024 "/usr/share/dict/words" (numlines :: (Monad m) => Iteratee ByteString m Int)
result <- run i'
print result
答案 0 :(得分:3)
所以我做了一些实验,得到的wc -l只有“wc -l”的两倍慢。这比上面显示的wc -c版本性能更好。
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as BSL
import qualified Data.ByteString.Char8 as BS
import qualified Data.Enumerator as E
import qualified Data.Enumerator.Binary as EB
import Control.Monad.IO.Class (liftIO)
import Data.Int
numlines :: Int64 -> E.Iteratee BS.ByteString IO ()
numlines n = do
chunk <- EB.take 1024
case chunk of
"" -> do liftIO $ print n
return ()
a -> do let ct = BSL.count '\n' a
numlines (n+ct)
main = do
let i = EB.enumFile "/usr/share/dict/words" E.$$ numlines 0
E.run_ i
运行它与本机:
Eriks-MacBook-Air:skunk erikhinton$ time wc -l "/usr/share/dict/words"
235886 /usr/share/dict/words
real 0m0.009s
user 0m0.006s
sys 0m0.002s
Eriks-MacBook-Air:skunk erikhinton$ time ./wcl
235886
real 0m0.019s
user 0m0.013s
sys 0m0.005s
[编辑]
这是一个更快,更小的足迹和更加简洁/富有表现力的方式。这些调查员开始变得有趣。
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as BSL
import qualified Data.ByteString.Char8 as BS
import qualified Data.Enumerator as E
import qualified Data.Enumerator.Binary as EB
import qualified Data.Enumerator.List as EL
import Control.Monad.IO.Class (liftIO)
import Data.Int
numlines :: E.Iteratee BS.ByteString IO ()
numlines = do
num <- EL.fold (\n b -> (BS.count '\n' b) + n ) 0
liftIO . print $ num
main = do
let i = EB.enumFile "/usr/share/dict/words" E.$$ numlines
E.run_ i
时间
Eriks-MacBook-Air:skunk erikhinton$ time ./wcl2
235886
real 0m0.015s
user 0m0.010s
sys 0m0.004s
答案 1 :(得分:1)
如果您正在阅读ByteString
个块,则可以使用count
中的Data.ByteString
功能,相关步骤将为
step !i (Chunk xs) = liftI (step $ i + count 10 xs)
(也许是一个fromIntegral)。 Data.ByteString.count
非常快,不应该比wc -l慢得多。
答案 2 :(得分:1)
我想出了如何修复类型错误。修复类型错误的关键是理解 Data.ListLike.filter 和 ByteString 输入之间的关系传递给那个过滤器。这是Data.ListLike.filter的类型:
Data.ListLike.filter
:: Data.ListLike.Base.ListLike full item =>
(item -> Bool) -> full -> full
完整 是指枚举器/ iteratee上下文中的流,如果我理解正确的话。 item 是指流的元素。
现在,如果我们想要在输入文件中过滤换行符,我们必须知道输入文件流的类型以及该流中元素的类型。在这种情况下,输入文件将被读取为 ByteString 流。 ByteString 被记录为Word8向量的节省空间的表示。所以,这里的 item 类型是Word8。
因此,当我们编写过滤器时,在step函数中,我们必须确保为Word8定义了Bool操作,因为这是传递给过滤器的项的类型(如上所述)。我们正在过滤换行。因此,bool函数就像下面构建一个Word8表示换行符的bool函数,并检查与Word8类型的x的相等性,应该可以工作:
\x -> x == Data.ByteString.Internal.c2w '\n'
还有一个缺失的部分 - 由于某些原因,编译器(v7.0.3 Mac)无法推断出numfile类型签名中 el 的类型(如果有人对它的原因有所了解)是这样,请讨论)。因此,明确告诉它是Word8解决了编译问题:
numlines :: (Monad m, Num a, LL.ListLike s Word8) => Iteratee s m a
下面的完整代码 - 它编译并运行得非常快。
{-# LANGUAGE BangPatterns,FlexibleContexts #-}
import Data.Iteratee as I
import Data.ListLike as LL
import Data.Iteratee.IO
import Data.ByteString
import GHC.Word (Word8)
import Data.ByteString.Internal (c2w)
numlines :: (Monad m, Num a, LL.ListLike s Word8) => Iteratee s m a
numlines = liftI $ step 0
where
step !i (Chunk xs) = let newline = c2w '\n' in liftI (step $i + fromIntegral (LL.length $ LL.filter (\x -> x == newline) xs))
step !i stream = idone i stream
{-# INLINE numlines #-}
main = do
i' <- enumFile 1024 "/usr/share/dict/words" (numlines :: (Monad m) => Iteratee ByteString m Int)
result <- run i'
print result
{- Time to run on mac OSX:
$ time ./test ## above compiled program: ghc --make -O2 test.hs
235886
real 0m0.011s
user 0m0.007s
sys 0m0.004s
$ time wc -l /usr/share/dict/words
235886 /usr/share/dict/words
real 0m0.005s
user 0m0.002s
sys 0m0.002s
-}
答案 3 :(得分:1)
已经有很多好的答案;我很少提供性能方面,但有一些风格点。
首先,我会这样写:
import Prelude as P
import Data.Iteratee
import qualified Data.Iteratee as I
import qualified Data.Iteratee.IO as I
import qualified Data.ByteString as B
import Data.Char
import System.Environment
-- numLines has a concrete stream type so it's not necessary to provide an
-- annotation later. It could have a more general type.
numLines :: Monad m => I.Iteratee B.ByteString m Int
numLines = I.foldl' step 0
where
--step :: Int -> Word8 -> Int
step acc el = if el == (fromIntegral $ ord '\n') then acc + 1 else acc
main = do
f:_ <- getArgs
words <- run =<< I.enumFile 65536 f numLines
print words
最大的区别在于它使用Data.Iteratee.ListLike.foldl'
。请注意,只有各个流元素对步骤函数很重要,而不是流类型。它与您使用的功能完全相同。 Data.ByteString.Lazy.foldl'
。
使用foldl'
也意味着您不需要使用liftI
手动编写迭代。除非绝对必要,否则我会阻止用户这样做。结果通常更长,更难维持,几乎没有任何好处。
最后,我显着增加了缓冲区大小。在我的系统上,这比enumerator
的默认值4096稍微快一点,这比你选择的1024更快(使用iteratee)。当然,YMMV有这个设置。