2014-04-19 15 views
7

Tôi đang viết một chương trình trong đó tệp đầu vào được chia thành nhiều tệp (Lược đồ chia sẻ bí mật của Shamir).Conduit - Nhiều tập tin đầu ra trong đường ống

Dưới đây là các đường ống dẫn Tôi đang tưởng tượng:

  • nguồn: sử dụng Conduit.Binary.sourceFile để đọc từ đầu vào
  • ống dẫn: Mất một ByteString, sản xuất [ByteString]
  • sink: Lấy [ByteString] từ ống dẫn và ghi mỗi ByteString (trong [ByteString]) vào tệp tương ứng của chúng. (Nói nếu đầu vào [ByteString] của chúng tôi được gọi là BSL, sau đó bsl !! 0 sẽ được ghi vào tập tin 0, bsl !! 1 nộp 1 và vân vân)

Tôi tìm thấy một câu hỏi liên quan file nhiều đầu vào here, nhưng trong trường hợp của họ toàn bộ đường ống được chạy một lần cho mỗi tệp đầu vào, trong khi đối với chương trình của tôi, tôi ghi vào nhiều tệp đầu ra trong đường dẫn.

Tôi cũng đang xem qua mã nguồn Conduit here để xem liệu tôi có thể triển khai multiSinkFile bản thân mình hay không, nhưng tôi hơi bối rối bởi loại tiêu dùng của sinkFile và hơn thế nữa nếu tôi cố gắng đào sâu hơn ... (Tôi vẫn là người mới bắt đầu)

Vì vậy, câu hỏi đặt ra là, tôi nên thực hiện một hàm như multiSinkFile cho phép nhiều tệp được viết như một phần của bồn rửa như thế nào?

Bất kỳ mẹo nào được đánh giá cao!

Làm rõ

Hãy nói rằng chúng tôi muốn làm việc chia sẻ bí mật Shamir về các tập tin có chứa giá trị nhị phân của "ABCDEF" (thành 3 phần).

(Vì vậy, chúng tôi có tập tin của chúng tôi vào srcFile và các tập tin đầu ra của chúng tôi outFile0, outFile1outFile2)

Đầu tiên chúng ta đọc "ABC" từ tập tin, và thực hiện xử lý mà sẽ cung cấp cho chúng tôi một danh sách, nói, ["133", "426", "765"]. do đó "133" sẽ được ghi vào outFile0, "426" đến outFile1"765" đến outFile2. Và sau đó chúng tôi đọc "DEF" từ srcFile, xử lý trên đó và ghi kết quả đầu ra tương ứng vào mỗi tệp đầu ra.

EDIT:

Cảm ơn câu trả lời của bạn. Tôi đã đôi khi để hiểu những gì đang xảy ra với ZipSinks vv, và tôi đã viết một chương trình thử nghiệm đơn giản lấy đầu vào của tệp nguồn và chỉ cần ghi nó vào 3 tệp đầu ra. Hy vọng rằng điều này sẽ giúp những người khác trong tương lai.

{-# 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 
+1

Bạn có thể cụ thể hơn về cách đầu ra không? Bạn có muốn tạo ra một tệp hoàn chỉnh, sau đó một tệp khác, v.v. không? Hoặc bạn có muốn tạo ra nội dung của họ cùng một lúc, đó là một số dữ liệu để nộp 1, một số dữ liệu để tập 2, sau đó một số dữ liệu để nộp 1 vv? –

+0

Tôi đã thêm một số làm rõ cho câu hỏi. Hy vọng rằng sẽ giúp. –

Trả lời

6

Có một số cách để làm điều đó, tùy thuộc vào việc bạn có muốn tăng số lượng tệp bạn đang viết động hay chỉ giữ một số cố định.Dưới đây là một ví dụ với một danh sách cố định các tập tin:

{-# 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) 

Bạn có thể try this online with FP School of Haskell.

+0

Cảm ơn câu trả lời của bạn. Tôi có đúng khi nói rằng bạn đang sử dụng 'idxSinkFile' để tạo các bồn chứa chỉ có' ​​ByteString' tương ứng trong '[ByteString] đã cho, và sau đó sử dụng' ZipSink + otraverse_' để tạo ra một bồn rửa đơn lẻ trong tất cả họ? –

+0

Trên 'idxSinkFile', Khi chúng ta chạy' concatMapC (flip atMay idx) 'trên nói' '" "aaa", "bbb", "ccc"] 'instance trong đó' idx' là 0, chúng ta sẽ kết thúc bằng "aaa "được đưa vào tệp sink' fp' ('file0.txt'). –

+0

Để bình luận đầu tiên của bạn: có. Để bình luận thứ hai của bạn: Tôi không chắc chắn nếu đó là một câu hỏi. –

8

Một khả năng là để thuật toán của bạn xuất ra một cái gì đó như (Int, ByteString), trong đó Int là chỉ mục của tệp đầu ra được chỉ định (tất nhiên bạn có thể sử dụng bất kỳ loại nào khác làm khóa). Bằng cách này, ống dẫn có thể quyết định tập tin nào muốn nối thêm đầu ra của nó.

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..] 

Cập nhật: Ví dụ sau đây cho thấy cách multiSink_ có thể được sử dụng (các bồn thử nghiệm chỉ cần in tất cả mọi thứ để thiết bị xuất chuẩn với một tiền tố phù hợp, thay vì viết tập tin).

-- | 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: "]) 
+0

Trong mã của bạn, bạn đang gợi ý rằng tôi trong ống dẫn * xử lý của tôi * tôi chạy lệnh 'yield', mỗi cặp với một cặp' (Int, ByteString) 'khác nhau để đạt được nhiều đầu ra tệp? –

+1

@jtcwang Chính xác. Điều này cho phép ống dẫn của bạn quyết định tập tin vào thời điểm cần viết. Vì vậy, bạn có thể viết một số đoạn vào một tập tin, sau đó một số đoạn khác nhau, hoặc xen kẽ chúng, mà không có bất kỳ hạn chế. Tôi đang thêm một ví dụ nhỏ vào mã. –

+0

Tôi nghĩ rằng đây là một cách tiếp cận thực sự tốt. –

Các vấn đề liên quan