使用折叠处理包装线

时间:2014-01-20 16:37:06

标签: f# fold

我有一个在特定行号上有硬中断的行列表,如下所示:

Header:<SmallJson>
Header:<VeryLongJson...
....>
Header:<Json>

我需要处理它以便删除换行符,所以它变成这样:

Header:<SmallJson>
Header:<VeryLongJson.......>
Header:<Json>

我想出了一个解决方案,但我并不是特别高兴:

let rawLines = [ "Header:<SmallJson>"
                 "Header:<VeryLongJson..."
                 "....>"
                 "Header:<Json>" ]

let processedLines = 
    (([], ""), rawLines)
    ||> List.fold (fun (result, state) line -> 
        if line.StartsWith "Header:"
        then state::result, line
        else result, state + line)
    |> (fun (result, state) -> state::result)
    |> List.rev
    |> List.tail

有没有办法以更简单的方式实现这一目标?折叠结束时的额外state::result和List.tail特别惹恼了我。优选不使用突变

3 个答案:

答案 0 :(得分:3)

这基本上是“分块”问题,已经在SO上有一些好的答案。我喜欢Brian的方法here,根据你的问题,它会是:

[ let linesToJoin = ResizeArray()
  for line in rawLines do
    if line.StartsWith("Header:") && linesToJoin.Count > 0 then
      yield String.Join("", linesToJoin)
      linesToJoin.Clear()
    linesToJoin.Add(line) 
  if linesToJoin.Count > 0 then
    yield String.Join("", linesToJoin) ]

它不是更优雅,但我认为意图更清晰。

另一种选择是使用Tomas' groupWhen functionsee usage)。

答案 1 :(得分:2)

你可以做一个非常相似的尾递归函数,并避免这两个步骤:

let rec combineLines (currentLine:string) combinedLines = function
| (line:string)::tail when line.StartsWith "Header:" && currentLine <> "" -> 
    combineLines line (currentLine::combinedLines) tail
| line::tail -> combineLines (currentLine + line) combinedLines tail
| [] -> currentLine::combinedLines

lines |> combineLines "" [] |> List.rev

答案 2 :(得分:2)

如果您使用foldBack从最后处理,这实际上更简单,特别是您不需要反转结果,因此它应该更快:

let processedLines =
    (rawLines, ("", []))
    ||> List.foldBack (fun line (currentLine, allLines) ->
        if line.StartsWith "Header:" then
            "", line + currentLine :: allLines
        else
            line + currentLine, allLines)
    |> function
        | "", lines -> lines
        | _ -> failwith "The original string didn't start with 'Header:'"