纯模式匹配

时间:2017-02-09 15:31:14

标签: f# pattern-matching

我正在构建一个函数,该函数在nth位置之后的字符串中出现多次字符数。

countCh ("aaabbbccc", 3, 'b')
val it: int = 2

C中,我会使用带有while循环的累加器。但我正在努力学习F#功能面,不鼓励这种方法。 所以我使用警卫来测试几个条件并构建函数:

let rec countCh (s:string, n:int, ch:char) =
match s, n, ch with
   | (s, n, ch) when n > s.Length -> 0                          //p1
   | (s, n, ch) when n < 0 -> 0                                 //p2
   | (s, n, ch) when s.[n] <> ch -> countCh(s, n + 1, ch)       //p3
   | (s, n, ch) when s.[n] = ch -> 1 + countCh(s, n + 1, ch)    //p4

模式34的共存是有问题的(恐怕不可能)。即使它编译,我也无法使它工作。如何在功能上处理这项任务?

3 个答案:

答案 0 :(得分:4)

首先,这些分支的共存不成问题。它们不会相互冲突。为什么你认为它有问题?是因为您收到“不完整模式匹配”编译器警告?该警告并未告诉您分支冲突,它告诉您编译器无法证明四个分支涵盖所有可能性。或者你认为这是出于其他原因吗?如果您希望准确回答问题,则必须更清楚地询问他们。

其次,你在滥用模式匹配。看:没有图案!每个分支中的模式完全相同,而且是微不足道的。只有警卫是不同的。这在match内看起来非常违反直觉,但可以用if..elif明确表达:

let rec countCh (s:string) n ch =
   if n >= s.Length || n < 0 then 0
   elif s.[n] = ch then 1 + countCh s (n + 1) ch
   else countCh s (n + 1) ch

注意1 :看看我如何将参数调整为咖喱?除非有非常强烈的理由使用tupled,否则始终使用curried形式。咖喱参数在呼叫方使用起来更加方便。

注意2 :您的条件n > s.Length不正确:字符串索引从0转到s.Length-1,因此保释条件应为n >= s.Length 。它已在我的代码中得到纠正。

最后,由于这是一个练习,我必须指出递归不是 tail 递归。查看第二个分支(在我的代码中):它以递归方式调用函数,然后在结果中添加一个。由于您必须对递归调用的结果执行某些操作,因此递归不能是“ tail ”。这意味着您在很长的输入上存在堆栈溢出的风险。

要将其转换为 tail 递归,您需要将函数“内向外”,这样说。您不需要从每次调用返回结果,而是需要将传递给每个调用(也称为“累加器”),并且只从终端案例返回:

let rec countCh (s:string) n ch countSoFar =
   if n >= s.Length || n < 0 then countSoFar
   elif s.[n] = ch then countCh s (n+1) ch (countSoFar+1)
   else countCh s (n+1) ch countSoFar

// Usage:
countCh "aaaabbbccc" 5 'b' 0

这样,每个递归调用都是“最后一次”调用(即函数不对结果做任何事情,而是直接将其传递给自己的调用者)。这称为“尾递归”,可以编译为在常量堆栈空间中工作(而不是线性)。

答案 1 :(得分:2)

我建议不要自己写,但要求图书馆功能寻求帮助:

let countCh (s: string, n, c) = 
    s.Substring(n+1).ToCharArray()
    |> Seq.filter ((=) c)
    |> Seq.length

或者使用Seq.skip,以及您可以将转换删除到字符数组的事实:

let countCh (s: string, n, c) = 
    s
    |> Seq.skip (n + 1)
    |> Seq.filter ((=) c)
    |> Seq.length

答案 2 :(得分:2)

我同意其他答案,但我想帮助您解决原始问题。你需要缩进该函数,并且你有一个错误:

let rec countCh (s:string, n:int, ch:char) =
    match s, n, ch with
        | s, n,  _ when n >= s.Length-1 -> 0                        //p1
        | s, _,  _ when n < 0 -> 0                                  //p2
        | s, n, ch when s.[n+1] <> ch -> countCh(s, n+2, ch)        //p3
        | s, n, ch when s.[n+1] = ch -> 1 + countCh(s, n+2, ch)     //p4