8

入力ファイルが複数のファイルに分割されるプログラムを作成しています (シャミールの秘密共有スキーム)。

私が想像しているパイプラインは次のとおりです。

  • source: Conduit.Binary.sourceFile を使用して入力から読み取ります
  • コンジット: ByteString を受け取り、[ByteString] を生成します
  • シンク:コンジットから [ByteString] を取得し、各 ByteString ([ByteString] 内) を対応するファイルに書き込みます。(たとえば、入力 [ByteString] が bsl と呼ばれる場合、bsl !! 0ファイル 0、bsl !! 1ファイル 1 などに書き込まれます)

ここで複数の入力ファイルに関する質問を見つけましたが、その場合、入力ファイルごとにパイプライン全体が 1 回実行されますが、私のプログラムでは、パイプライン内の複数の出力ファイルに書き込みます。

また、ここでConduit のソース コードを調べて、自分で multiSinkFile を実装できるかどうかを確認していますが、sinkFile の Consumer 型に少し戸惑っています。まだ初心者です)

問題は、複数のファイルをシンクの一部として書き込むことを可能にする multiSinkFile のような関数をどのように実装すればよいかということです。

どんなヒントでも大歓迎です!

明確化

「ABCDEF」のバイナリ値 (3 つの部分) を含むファイルで Shamir の秘密共有を行いたいとします。

(つまり、入力ファイルsrcFileと出力ファイルoutFile0outFile1ありますoutFile2)

最初にファイルから「ABC」を読み取り、たとえば、のリストを取得する処理を行います["133", "426", "765"]。soは to 、to 、to"133"に書き込まれます。そして、 から「DEF」を読み取り、処理を行い、対応する出力を各出力ファイルに書き込みます。outFile0"426"outFile1"765"outFile2srcFile

編集:

回答ありがとうございます。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

1 つの可能性は、アルゴリズムが のようなものを出力できるよう(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_使用方法を示しています (テスト シンクは、ファイルを書き込む代わりに、適切なプレフィックスを付けてすべてを stdout に出力するだけです)。

-- | 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 に答える