我正在尝试使用现有的MapReduce实现编写一个简单的程序( Real World Haskell 中的实现)。
作为使用框架的一个例子,这里有一些代码来计算文件中的单词数:
module Main where
import Control.Monad (forM_)
import Data.Int (Int64)
import qualified Data.ByteString.Lazy.Char8 as LB
import System.Environment (getArgs)
import LineChunks (chunkedReadWith)
import MapReduce (mapReduce, rdeepseq)
wordCount :: [LB.ByteString] -> Int
wordCount = mapReduce rdeepseq (length . LB.words)
rdeepseq sum
main :: IO ()
main = do
args <- getArgs
forM_ args $ \path -> do
numWords <- chunkedReadWith wordCount path
putStrLn $ "Words: " ++ show numWords
我需要使用相同的MapReduce框架来编写一个搜索某些字符串的程序(比如“il”),然后返回找到它们的行号。例如,输出可能是:
verILy: found on lines 34, 67, 23
ILlinois: found on lines 1, 2, 56
vILla: found on lines 4, 5, 6
(“il”的大写不是必需的。)
我是Haskell的初学者,还没有任何具体的想法。我注意到Data.ByteString.Lazy.Char8类有一个成员函数findIndices
。是否可以使用它?
非常感谢正确方向的任何代码或提示。
答案 0 :(得分:3)
好的,让我们点击这个。
首先,我们需要一种方法来查找字符串中的单词。这可以使用Regex来完成,比方说regex-tdfa
包。 Haskell正则表达式包很不错,但是非常通用,一开始有点难以阅读。我们将创建一个函数,它只是匹配运算符(=~)
的一个包装器,主要是为了使类型具体化。
wordHere :: LB.ByteString -> LB.ByteString -> Bool
wordHere word string = string =~ word
-- a.k.a. = flip (=~)
现在,mapReduce
通过向不同的火花提供大量([a] -> c)
类型的并行本地“映射”作业来分解像(a -> b)
这样的列表,然后将reduce作业中的所有结果折叠为([b] -> c)
。一般来说,不能保证减少步骤的 order ,但RWH的mapReduce
实际上确实给了我们保证。
RWH的lineChunks
函数实际上将文档(LB.ByteString - &gt; [LB.ByteString])拆分为少量行的块。我们的映射作业各自获得其中一个块,并且需要在本地提供有关行匹配的信息。我们可以通过将块分成它们的组成行来进行,本地对行进行编号,将wordHere
映射到它们上,并收集wordHere
返回true的本地行号。我们通常会首先执行此操作,将wordHere
替换为任何谓词p :: (LB.ByteString -> Bool)
import Control.Arrow
localLinesTrue :: (LB.ByteString -> Bool) -> [LB.ByteString] -> [Int]
localLinesTrue p ls = map fst . filter snd . map (second p) . zip [1..]
现在我们可以创建像localLinesTrue (wordHere $ LB.pack "foobar") . LB.lines :: LB.ByteString -> [Int]
这样的本地地图制作工具。
给定该类型的映射器也会稍微阐明函数其余部分的类型。我们现在有
>>> :t mapReduce rdeepseq (localLinesTrue (wordHere "foo")) rdeepseq
... :: ([[Int]] -> c) -> [LB.ByteString] -> c
因此我们的reducer必须是([[Int]] -> c)
类型。很酷,让我们尝试做到这一点。如果我们有一个Int
列表,我们可以重建实际的行号......
[[], [], [], [5], [3], [], []]
等等,实际上,我们做不到。我们需要在mapper中添加更多信息 - 每个块中出现的行数。幸运的是,由于我们小心翼翼地保持我们的东西解开,这很容易添加。我们需要一个更像([Int], Int)
的返回类型,其中第二个参数是该块的行数。我们可以通过“扇出”(&&&)
来做到这一点。
mapper regex = (localLinesTrue (wordHere regex) &&& length) . LB.lines
现在我们的输出看起来像
[ ([], 3), ([], 5), ([3, 5], 10), ... ]
我们可以实现一个使用State
monad
reducerStep :: ([Int], Int) -> State Int [Int]
reducerStep (hits, count) = do pos <- get
modify (+count)
return (map (+pos) hits)
reducer :: [([Int], Int)] -> [Int]
reducer = concat . evalState 0 . mapM reducerStep
我们已经
了mapReduce rdeepseq (mapper regex)
rdeepseq reducer
:: [LB.ByteString] -> [Int]
应该让你在95%的路上走到最后。