6

作为项目的一部分,我将自己指定为提高我对 F# 和函数式编程的一般知识的一种方式,我正在尝试从头开始编写字符串模式匹配算法,而不使用任何循环或变量(或正则表达式或字符串.替换和朋友)。由于这纯粹是一个学习项目,我对最好的方法不感兴趣,只对最好的功能方法感兴趣。

我正在尝试编写一个接受通配符、模式字符串和输入字符串作为参数的函数。如果模式与输入不匹配,则函数返回None. 如果模式与输入匹配,则该函数返回输入字符串Some(str)str任何部分与模式字符串中可能存在的任何通配符匹配的位置。

我大部分时间都在工作,稍后我会包含代码。我编写了一个通用模式匹配函数,它适用于任何支持相等的通用列表,然后是一个辅助函数,它接受字符串并将字符列表传递给通用函数。这一切都有效,除了一件事:模式字符串中对多个通配符的支持不是很好 - 它获取每个通配符的匹配项并将它们连接到输出中的单个字符串中。

例如:

> strMatch '*' "foo" "bar";;
val it : string option = None

> strMatch '*' "test" "test";;
val it : string option = Some ""

> strMatch '*' "functional programming is *" "functional programming is fun";;
val it : string option = Some "fun"

> strMatch '*' "* and *" "you and me";;
val it : string option = Some "youme"

这是我要修复的最后一个。理想情况下,我想返回一个字符串列表而不是单个字符串,列表中的每个元素都是与一个通配符匹配的字符串。如果做不到这一点,我可能会使用只返回第一个通配符匹配的版本 - 这是我需要摆脱的两个通配符的连接值。我只是不太确定如何处理它。

因此,如果有人可以建议我如何根据它们匹配的通配符对返回值进行分组,我将不胜感激。我也对您可能想要建议的对我的代码的任何其他改进感兴趣。

let rec doMatch (wildcard:'a) (pat:'a list) (input:'a list) : 'a list option =
    let singleMatch p i =
        match (p, i) with
        | phd :: ptl, ihd :: itl ->
            if phd = wildcard then
                match doMatch wildcard ptl itl with
                | None -> None
                | Some x -> Some(ihd :: x)
            else None
        | _ -> None

    let longerMatch p i =
        match (p, i) with
        | phd :: ptl, ihd :: itl ->
            if phd = wildcard then
                match doMatch wildcard p itl with
                | None -> None
                | Some x -> Some(ihd :: x)
            else None
        | _ -> None

    match (pat, input) with
    | [], [] -> Some([])
    | [], _::_ -> None
    | _::_, [] -> None
    | phd :: ptl, ihd :: itl ->
        if phd <> wildcard then
            if phd = ihd then doMatch wildcard ptl itl
            else None
        else
            match singleMatch pat input with
            | Some x -> Some(x)
            | None -> longerMatch pat input

let strMatch (wildcard:char) (pat:string) (input:string) =
    match doMatch wildcard (List.ofSeq pat) (List.ofSeq input) with
    | None -> None
    | Some x -> Some(new string(Array.ofList x))

您可能已经猜到了,但这是 F# 中 Eliza 聊天机器人实现的一部分。

4

1 回答 1

4

从设计的角度来看,我喜欢返回一个

'a list option

例如,在哪里

None              // it did not match
Some[]            // matched, input had 0 wildcards
Some["foo";"bar"] // matched, input has 2 wildcards, "foo" matched 1st, "bar" 2nd

即只要保证返回 'Some' 时,列表的长度等于通配符的个数,并且列表的元素是按顺序匹配的。在我看来,这似乎很容易实现,并且客户端代码使用/消费也是合理的。

(我不清楚你的长文中是否有更深层次的问题。)

看起来很有趣的东西!

编辑

这是一些更新的代码。我的直觉告诉我这并不完全正确,但它至少适用于您的示例。关键是用

'a list list option

因为 'a 是一个字符,所以 'a 列表就像一个字符串,我们想要一个字符串列表。singleMatch 开始一个新的字符串列表,而 longMatch 是在当前字符串的前面。

let rec doMatch (wildcard:'a) (pat:'a list) (input:'a list) 
           : 'a list list option =
    let singleMatch p i =
        match (p, i) with
        | phd :: ptl, ihd :: itl ->
            if phd = wildcard then
                match doMatch wildcard ptl itl with
                | None -> None
                | Some xs -> Some([ihd]::xs)
            else None
        | _ -> None

    let longerMatch p i =
        match (p, i) with
        | phd :: ptl, ihd :: itl ->
            if phd = wildcard then
                match doMatch wildcard p itl with
                | None -> None
                | Some ([]) -> Some([[ihd]])
                | Some (x::xs) -> Some((ihd :: x)::xs)
            else None
        | _ -> None

    match (pat, input) with
    | [], [] -> Some([])
    | [], _::_ -> None
    | _::_, [] -> None
    | phd :: ptl, ihd :: itl ->
        if phd <> wildcard then
            if phd = ihd then doMatch wildcard ptl itl
            else None
        else
            match singleMatch pat input with
            | Some x -> Some(x)
            | None -> longerMatch pat input

let strMatch (wildcard:char) (pat:string) (input:string) =
    match doMatch wildcard (List.ofSeq pat) (List.ofSeq input) with
    | None -> None
    | Some x -> Some(x|>List.map (fun chList -> new string(Array.ofList chList)))

printfn "%A" (strMatch '*' "foo" "bar")
printfn "%A" (strMatch '*' "test" "test")
printfn "%A" (strMatch '*' "functional programming is *" 
                           "functional programming is fun")
printfn "%A" (strMatch '*' "* and *" "you and me")
于 2009-11-08T19:33:13.013 回答