列出中间状态的处理

时间:2011-09-22 15:46:56

标签: f#

我正在处理字符串列表,您可以将它们视为一本书的行。 当一行为空时,必须将其丢弃。当它是标题时,它被“保存”为当前标题。每个“普通”行必须生成一个包含其文本和当前标题的对象。 最后,你有一个行列表,每行都有相应的标题。

例:

- Chapter 1

Lorem ipsum dolor sit amet
consectetur adipisicing elit

- Chapter 2

sed do eiusmod tempor
incididunt u

第一行是标题,必须丢弃第二行,然后将两行保存为段落,每行以“第1章”作为标题。等等。最终得到类似于:

的集合
{"Lorem ipsum...", "Chapter 1"},
{"consectetur...", "Chapter 1"},
{"sed do...", "Chapter 2"},
{"incididunt ...", "Chater 2"}

我知道标题/段落模型没有100%的意义,但我简化了模型以说明问题。

这是我的迭代解决方案:

let parseText allLines =
    let mutable currentTitle = String.Empty
    seq {
        for line in allLines do
            match parseLine line with
            | Empty -> 0 |> ignore
            | Title caption ->
                currentTitle <- caption
            | Body text ->
                    yield new Paragraph(currentTitle, text)
    }

第一个问题是我必须丢弃空行,我用0 |> ignore来做,但对我来说这看起来很糟糕。有什么做到这一点(没有预先过滤列表)?

此函数的尾递归版本很简单:

let rec parseText allLines currentTitle paragraphs =
    match allLines with
    | [] -> paragraphs
    | head :: tail -> 
        match head with
        | Empty -> parseText tail currentTitle paragraphs
        | Title caption -> parseText tail caption paragraphs
        | Body text -> parseText tail currentTitle (new Paragraph(currentTitle, text) :: tail)

问题:

  • 这两个版本之间是否存在显着差异 (式/性能/等)?
  • 有没有更好的方法来解决这个问题 问题?是否可以使用单个List.map?

3 个答案:

答案 0 :(得分:2)

您可以将0 |> ignore替换为()(单位),这是一个无操作。两个实现之间的最大区别是第一个是懒惰的,这可能对大输入有用。

以下内容对您也有用(这是我能想到的最简单的解决方案):

let parseText (lines:seq<string>) =
  lines
  |> Seq.filter (fun line -> line.Trim().Length > 0)
  |> Seq.pairwise (fun (title, body) -> Paragraph(title, body))

如果没有,也许这会奏效:

let parseText (lines:seq<string>) =
  lines
  |> Seq.choose (fun line -> 
    match line.Trim() with
    | "" | null -> None
    | Title title -> Some title
    | Body text -> Some text)
  |> Seq.pairwise (fun (title, body) -> Paragraph(title, body))

答案 1 :(得分:2)

虽然不是一个List.Map,但这是我提出的解决方案:

let parseText allLines = 
    allLines 
    |> Seq.fold (fun (currentTitle,paragraphs) line -> 
        match parseLine line with
        | Empty -> currentTitle,paragraphs
        | Title caption -> caption,paragraphs
        | Body text -> String.Empty,Paragraph(currentTitle, text)::paragraphs
        ) (String.Empty,[])
    |> snd

我正在使用(currentTitle,paragraphs)作为状态的折叠。 snd用于提取结果(它是状态元组的 s eco nd 部分)。

当您在F#中进行大部分处理时,使用列表非常诱人,但其他数据结构,甚至普通序列都有其用途。

顺便说一下,您的序列码是否编译?我必须将mutable currentTitle = String.Empty替换为currentTitle = ref String.Empty

答案 2 :(得分:1)

下面是一个这样的实现(虽然没有经过测试,但我希望它能给你这个想法)

let isNotEmpty l = match l with
                   | Empty -> false
                   | _ -> true

let parseText allLines =
    allLines |> Seq.map parseLine |> Seq.filter isNotEmpty
    |> Seq.scan (fun (c,t,b) i -> match i with
                                  | Title tl -> (0,tl,"")
                                  | Body bb -> (1,t,bb) 
                                  | _ -> (0,t,b)) (0,"","")
    |> Seq.filter (fun (c,_,_) -> c > 0)
    |> Seq.map (fun (_,t,b) -> Paragraph(t,b) )