8

我正在编写一个程序,其中输入文件被拆分为多个文件(Shamir 的秘密共享方案)。

这是我想象的管道:

  • 来源:使用 Conduit.Binary.sourceFile 从输入中读取
  • 管道:接受一个 ByteString,产生 [ByteString]
  • sink:从管道中获取 [ByteString],并将每个 ByteString(在 [ByteString] 中)写入其相应的文件。(假设我们的输入 [ByteString] 被称为 bsl,那么bsl !! 0将被写入文件 0、bsl !! 1文件 1 等等)

我在这里发现了一个关于多个输入文件的问题,但在他们的情况下,整个管道为每个输入文件运行一次,而对于我的程序,我正在写入管道中的多个输出文件

我也在查看这里的 Conduit 源代码,看看我是否可以自己实现一个 multiSinkFile,但我对 sinkFile 的 Consumer 类型有点困惑,如果我尝试更深入地挖掘,更是如此......(我是还是初学者)

所以,问题是,我应该如何实现像 multiSinkFile 这样的函数,它允许将多个文件作为接收器的一部分写入?

任何提示表示赞赏!

澄清

假设我们要对包含二进制值“ABCDEF”(分为 3 部分)的文件进行 Shamir 的秘密共享。

(所以我们有我们的输入文件srcFile和我们的输出文件outFile0outFile1outFile2

我们首先从文件中读取“ABC”,然后进行处理,这将为我们提供一个列表,例如["133", "426", "765"]. 所以"133"将被写入到outFile0"426"outFile1"765"outFile2。然后我们从 中读取“DEF” srcFile,对其进行处理,并将相应的输出写入每个输出文件。

编辑:

谢谢您的回答。我花了一些时间来了解 ZipSinks 等的情况,并且我编写了一个简单的测试程序,它接受源文件的输入并将其简单地写入 3 个输出文件。希望这将在未来对其他人有所帮助。

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
import ClassyPrelude.Conduit 
import Safe (atMay)
import Text.Printf
import Filesystem.Path.CurrentOS (decodeString, encodeString)
import Control.Monad.Trans.Resource (runResourceT, ResourceT(..))

-- get the output file name given the base (file) path and the split number
getFileName :: FilePath -> Int -> FilePath
getFileName basePath splitNumber = decodeString $ encodeString basePath ++ "." ++ printf "%03d" splitNumber

-- Get the sink file, given a filepath generator (that takes an Int) and the split number
idxSinkFile :: MonadResource m
            => (Int -> FilePath)
            -> Int
            -> Consumer [ByteString] m ()
idxSinkFile mkFP splitNumber =
    concatMapC (flip atMay splitNumber) =$= sinkFile (mkFP splitNumber)

sinkMultiFiles :: MonadResource m
               => (Int -> FilePath)
               -> [Int]
               -> Sink [ByteString] m ()
sinkMultiFiles mkFP splitNumbers = getZipSink $ otraverse_ (ZipSink . idxSinkFile mkFP) splitNumbers

simpleConduit :: Int -> Conduit ByteString (ResourceT IO) [ByteString]
simpleConduit num = mapC (replicate num)

main :: IO ()
main = do
    let mkFP = getFileName "test.txt"
        splitNumbers = [0..2]
    runResourceT $ sourceFile "test.txt" $$ simpleConduit (length splitNumbers) =$ sinkMultiFiles mkFP splitNumbers
4

2 回答 2

8

一种可能性是让您的算法输出类似(Int, ByteString),其中Int是指定输出文件的索引(当然您可以使用任何其他类型作为键)。这样,管道可以决定它想要将其输出附加到哪个文件。

import Data.Conduit
import qualified Data.Conduit.List as C
import qualified Data.Foldable as F

-- | Filter only pairs tagged with the appropriate key.
filterInputC :: (Monad m, Eq k) => k -> Conduit (k, a) m a
filterInputC idx = C.filter ((idx ==) . fst) =$= C.map snd

-- | Prepend a given sink with a filter.
filterInput :: (Monad m, Eq k) => k -> Sink a m r -> Sink (k, a) m r
filterInput idx = (filterInputC idx =$)

-- | Given a list of sinks, create a single sink that directs received values
-- depending on the index.
multiSink_ :: (Monad m) => [Sink a m ()] -> Sink (Int, a) m ()
multiSink_ = getZipSink . F.sequenceA_ . fmap ZipSink
             . zipWith filterInput [0..]

更新:以下示例显示了如何multiSink_使用(测试接收器只是将所有内容打印到带有适当前缀的标准输出,而不是写入文件)。

-- | A testing sink that just prints its input, marking it with
-- a given prefix.
testSink :: String -> Sink String IO ()
testSink prefix = C.mapM_ (putStrLn . (prefix ++))

-- | An example that produces indexed output.
testSource :: (Monad m) => Source m (Int, String)
testSource = do
    yield (0, "abc")
    yield (0, "def")
    yield (1, "opq")
    yield (0, "0")
    yield (1, "1")
    yield (2, "rest")

main :: IO ()
main = testSource $$ multiSink_ (map testSink ["1: ", "2: ", "3: "])
于 2014-04-20T16:25:43.090 回答
6

有多种方法可以做到这一点,具体取决于您是要动态增加要写入的文件数量,还是只保持固定数量。这是一个带有固定文件列表的示例:

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns      #-}
import           ClassyPrelude.Conduit
import           Safe                  (atMay)

idxSinkFile :: MonadResource m
            => (Int -> FilePath)
            -> Int
            -> Consumer [ByteString] m ()
idxSinkFile mkFP idx =
    concatMapC (flip atMay idx) =$= sinkFile fp
  where
    fp = mkFP idx

sinkMultiFiles :: MonadResource m
               => (Int -> FilePath)
               -> [Int]
               -> Sink [ByteString] m ()
sinkMultiFiles mkFP indices = getZipSink $ otraverse_ (ZipSink . idxSinkFile mkFP) indices

someFunc :: ByteString -> [ByteString]
someFunc (decodeUtf8 -> x) = map encodeUtf8 [x, toUpper x, toLower x]

mkFP :: Int -> FilePath
mkFP 0 = "file0.txt"
mkFP 1 = "file1.txt"
mkFP 2 = "file2.txt"

src :: Monad m => Producer m ByteString
src = yieldMany $ map encodeUtf8 $ words "Hello There World!"

main :: IO ()
main = do
    let indices = [0..2]
    runResourceT $ src $$ mapC someFunc =$ sinkMultiFiles mkFP indices
    forM_ indices $ \idx -> do
        let fp = mkFP idx
        bs <- readFile fp
        print (fp, bs :: ByteString)

您可以通过 FP School of Haskell 在线尝试

于 2014-04-20T09:32:42.143 回答