8
(fileNameToCharStream "bigfile"
 |>> ヒューズ [長さ;
           splitBy (fun x -> x = ' ' || x = '\n') removeEmpty |>> 長さ;
           splitBy (fun x -> x = '\n') keepEmpty |>> 長さ;
         ]))
  (*fuse は 3 つの関数を「融合」して同時に実行します*)
 |> run 2 (*強制的に 2 つのスレッドで並行して実行*)
 |> (fun [num_chars; num_words; num_lines] ->
       printfn "%d %d %d"
           num_chars num_words, num_lines))

このコードを次のように機能させたいと考えています。元のストリームをちょうど真ん中で 2 つに分割します。次に、半分ごとに、長さ (つまり、文字数)、単語数、行数の 3 つのことを計算する個別の計算を実行します。ただし、単語を誤って分割した場合に問題が発生することは望ましくありません。これには注意が必要です。ファイルは 1 回だけ読み取る必要があります。

指定された関数と演算子 |>> をどのようにプログラムすればよいですか? 出来ますか?

4

1 に答える 1

8

かなりお願いしているようです。文字列操作の理解はあなたに任せますが、一連の操作を並行して実行する演算子を定義する方法を紹介します。

ステップ 1:fuse関数を作成する

ヒューズ関数は、複数の関数を使用して単一の入力をマップするように見えます。これは、次のように簡単に記述できます。

//val fuse : seq<('a -> 'b)> -> 'a -> 'b list
let fuse functionList input = [ for f in functionList -> f input]

すべてのマッピング関数が同じタイプである必要があることに注意してください。

ステップ 2: 関数を並列実行する演算子を定義する

標準の平行マップ関数は、次のように記述できます。

//val pmap : ('a -> 'b) -> seq<'a> -> 'b array
let pmap f l =
    seq [for a in l -> async { return f a } ]
    |> Async.Parallel
    |> Async.RunSynchronously

私の知る限り、Async.Parallel非同期操作を並行して実行します。ここで、特定の時間に実行される並行タスクの数は、マシン上のコアの数と同じです (間違っている場合は誰かが私を修正できます)。したがって、デュアル コア マシンでは、この関数が呼び出されたときにマシン上で最大 2 つのスレッドが実行されている必要があります。コアごとに複数のスレッドを実行してもスピードアップは期待できないため、これは良いことです (実際には、余分なコンテキストの切り替えにより速度が低下する可能性があります)。

とで演算子を定義でき|>>ます。pmapfuse

//val ( |>> ) : seq<'a> -> seq<('a -> 'b)> -> 'b list array
let (|>>) input functionList = pmap (fuse functionList) input

そのため、|>>オペレーターは一連の入力を取得し、さまざまな出力を使用してそれらをマッピングします。これまでのところ、これらすべてをまとめると、次のようになります (fsi で)。

> let countOccurrences compareChar source =
    source |> Seq.sumBy(fun c -> if c = compareChar then 1 else 0)

let length (s : string) = s.Length

let testData = "Juliet is awesome|Someone should give her a medal".Split('|')
let testOutput =
    testData
    |>> [length; countOccurrences 'J'; countOccurrences 'o'];;

val countOccurrences : 'a -> seq<'a> -> int
val length : string -> int
val testData : string [] =
  [|"Juliet is awesome"; "Someone should give her a medal"|]
val testOutput : int list array = [|[17; 1; 1]; [31; 0; 3]|]

testOutputには 2 つの要素が含まれており、どちらも並行して計算されています。

ステップ 3: 要素を単一の出力に集約する

よし、これで配列の各要素で表される部分的な結果が得られたので、部分的な結果を 1 つの集計にマージしたいと考えています。入力の各要素は同じデータ型であるため、配列の各要素を同じ関数にマージする必要があると思います。

これは私が仕事のために書いた本当に醜い関数です:

> let reduceMany f input =
    input
    |> Seq.reduce (fun acc x -> [for (a, b) in Seq.zip acc x -> f a b ]);;

val reduceMany : ('a -> 'a -> 'a) -> seq<'a list> -> 'a list

> reduceMany (+) testOutput;;
val it : int list = [48; 1; 4]

reduceMany長さ n のシーケンスのシーケンスを取り、長さ n の配列を出力として返します。この関数を書くためのより良い方法を考えることができるなら、私のゲストになってください:)

上記の出力をデコードするには:

  • 48 = 2 つの入力文字列の長さの合計。元の文字列は 49 文字でしたが、「|」で分割されていることに注意してください。"|" ごとに 1 文字を消費しました。
  • 1 = 入力内の「J」のすべてのインスタンスの合計
  • 4 = 'O' のすべてのインスタンスの合計。

ステップ 4: すべてをまとめる

let pmap f l =
    seq [for a in l -> async { return f a } ]
    |> Async.Parallel
    |> Async.RunSynchronously

let fuse functionList input = [ for f in functionList -> f input]

let (|>>) input functionList = pmap (fuse functionList) input

let reduceMany f input =
    input
    |> Seq.reduce (fun acc x -> [for (a, b) in Seq.zip acc x -> f a b ])

let countOccurrences compareChar source =
    source |> Seq.sumBy(fun c -> if c = compareChar then 1 else 0)

let length (s : string) = s.Length

let testData = "Juliet is awesome|Someone should give her a medal".Split('|')
let testOutput =
    testData
    |>> [length; countOccurrences 'J'; countOccurrences 'o']
    |> reduceMany (+)
于 2009-09-30T18:04:01.473 に答える