4

ここにこの機能があります:

let ProcessFile (allLines: string list) = 
    let list = new List<List<string>>()

    let rec SplitFile (input: string list) =
        if input.Length <> 0 then
            list.Add(new List<string>(input.TakeWhile(fun x -> x <> "")))
            let nextGroup = input.SkipWhile(fun x -> x <> "").SkipWhile(fun x -> x = "")
            SplitFile (Seq.toList nextGroup)

    SplitFile allLines |> ignore
    list

ファイルの内容が文字列のリストとして与えられ、空の行で区切られた各グループを個別のリストとして取り、リストのリストを提供します。

私の質問は、 new List< List< string>> を使用する代わりに、文字列リストリストのようなものを私に与える利回りでこれを行うより良い方法はありますか? これは私には特にきちんとしていないようです。

4

4 に答える 4

5

より慣用的な解決策は次のとおりです。

let processFile xs =
  let rec nonEmpties n = function
    | [] as xs | ""::xs -> n, xs
    | _::xs -> nonEmpties (n+1) xs
  let rec loop xs =
    seq { match xs with
          | [] -> ()
          | ""::xs -> yield! loop xs
          | xs ->
              let n, ys = nonEmpties 0 xs
              yield Seq.take n xs
              yield! loop ys }
  loop xs

ここで、ネストされたnonEmpties関数は、指定されたリストの先頭にある空でない要素の数をカウントし、最後の空でない要素の後にカウントと末尾のリストの両方を返します。loop関数は空要素をスキップし、空でない要素のシーケンスを生成します.

このソリューションの興味深い特徴:

  • 空でない文字列の任意の長いシーケンスと空でない文字列のシーケンスのシーケンスを処理できるように、完全に末尾再帰的です。

  • 入力リストを参照することでコピーを回避します。

1,000 個の文字列の 1,000 シーケンスのテスト入力で、このソリューションは yamen の 8 倍、Tomas の 50% 高速です。

入力リストを配列に変換することから始めて、配列インデックスに作用する、さらに高速なソリューションを次に示します。

let processFile xs =
  let xs = Array.ofSeq xs
  let rec nonEmpties i =
    if i=xs.Length || xs.[i]="" then i else
      nonEmpties (i+1)
  let rec loop i =
    seq { if i < xs.Length then
            if xs.[i] = "" then
              yield! loop (i+1)
            else
              let j = nonEmpties i
              yield Array.sub xs i (j - i)
              yield! loop j }
  loop 0

1,000 文字列の 1,000 シーケンスのテスト入力では、このソリューションは yamen の 34 倍、Tomas の 6 倍高速です。

于 2012-06-27T12:16:49.390 に答える
2

あなたのコードは私にはかなり読みやすいですが、TakeWhileand をSkipWhile再帰的に使用するのはかなり非効率的です。これは単純な関数再帰ソリューションです。

let ProcessFile (allLines: string list) =
  // Recursively processes 'input' and keeps the list of 'groups' collected
  // so far. We keep elements of the currently generated group in 'current'  
  let rec SplitFile input groups current = 
    match input with 
    // Current line is empty and there was some previous group
    // Add the current group to the list of groups and continue with empty current
    | ""::xs when current <> [] -> SplitFile xs ((List.rev current)::groups) []
    // Current line is empty, but there was no previous group - skip & continue
    | ""::xs -> SplitFile xs groups []
    // Current line is non-empty - add it to the current group
    | x::xs -> SplitFile xs groups (x::current)
    // We reached the end - add current group if it is not empty
    | [] when current <> [] -> List.rev ((List.rev current)::groups)
    | [] -> List.rev groups

  SplitFile allLines  [] []

ProcessFile ["a"; "b"; ""; ""; "c"; ""; "d"]

本質的に同じコードは、次のように記述できseq { ... }ます。アキュムレータ ( ) を使用して現在のグループのリストを保持する必要がありますが、入力を反復処理するときにandをcurrent使用してグループを遅延して返します。yieldyield!

let ProcessFile (allLines: string list) =  
  let rec SplitFile input current = seq {
    match input with 
    | ""::xs when current <> [] -> 
        yield List.rev current
        yield! SplitFile xs []
    | ""::xs -> 
        yield! SplitFile xs []
    | x::xs -> 
        yield! SplitFile xs (x::current)
    | [] when current <> [] -> 
        yield List.rev current
    | [] -> () }

  SplitFile allLines []
于 2012-06-27T11:37:41.053 に答える
0

昔ながらのList.foldを使ってみませんか

let processFile lines =
([], lines) ||>
List.fold(fun acc l -> 
    match acc with
        | [] when l = "" -> acc        // filter empty lines at the start of the file
        | [] -> [[l]]                  // start the first group
        | []::xss when l = "" -> acc   // filter continous empty lines
        | xs::xss when l = "" ->       // found an empty line, start a new group
            let rxs = List.rev xs      // reverse the current group before starting a new one
            []::rxs::xss
        | xs::xss -> (l::xs)::xss)     // continue adding to the current group
|> List.rev
于 2012-06-27T17:08:03.493 に答える
0

個人的に、私は1つのライナーが好きです:

let source = ["a"; "b"; ""; ""; "c"; ""; "d"]

source                                                                       // can be any enumerable or seq
|> Seq.scan (fun (i, _) e -> if e = "" then (i + 1, e) else (i, e)) (0, "")  // add the 'index'
|> Seq.filter (fun (_, e) -> e <> "")                                        // remove the empty entries
|> Seq.groupBy fst                                                           // group by the index
|> Seq.map (fun (_, l) -> l |> Seq.map snd |> List.ofSeq)                    // extract the list only from each group (discard the index)
|> List.ofSeq                                                                // turn back into a list                      

ここでの最大の問題はSeq.groupBy、リスト全体をメモリに読み込むことですが、とにかくそれを実行しています。groupBy隣接するエントリのみを参照し、それで十分であり、代わりにファイルを入力できるようにする実装がありますSeq(たとえば、ではFile.ReadLinesなくを使用してFile.ReadAllLines)。

于 2012-06-27T11:53:44.497 に答える