【问题标题】:Wrapping a recursive function to count the number of function calls包装递归函数以计算函数调用的次数
【发布时间】:2010-11-14 00:15:44
【问题描述】:

假设我有一个递归函数,我想知道该函数在每个输入值中调用了多少次。与其放置 printf 表达式或更改返回类型以包含调用次数,是否可以用另一个函数“包装”函数来实现这一点?我希望包装函数返回函数调用的数量和原始函数结果。它应该可以跨不同的功能重用。

这是我拥有的,但它不起作用。

open System
open System.IO
open System.Collections.Generic

/// example recursive function
let rec getfilenames dir = 
    seq { 
        yield Directory.GetFiles dir
        for x in Directory.GetDirectories dir do yield! getfilenames x}

/// function to count the number of calls a recursive function makes to itself
let wrapped (f: 'a -> 'b) =
    let d = new Dictionary<'a, int>()

    fun x ->
        let ok, res = d.TryGetValue(x)
        if ok then d.[x] <- d.[x] + 1
        else
            d.Add(x, 1)
        d, f x

> let f = wrapped getfilenames
let calls, res = f "c:\\temp";;

val f : (string -> Dictionary<string,int> * seq<string []>)
val res : seq<string []>
val calls : Dictionary<string,int> = dict [("c:\temp", 1)]

【问题讨论】:

    标签: f#


    【解决方案1】:

    这是行不通的,因为getfilenames 被定义为调用getfilenames,而不是任何其他函数,尤其是之后定义的函数。因此,一旦您的包装器调用该函数,该函数就会忽略您的包装器并开始调用自己。

    您需要做的是将递归从getfilenames 函数中移到另一个函数中,方法是提供要递归调用的函数作为参数。

    let body recfun dir = 
        seq { 
            yield Directory.GetFiles dir
            for x in Directory.GetDirectories dir do yield! recfun x}
    
    let rec getfilenames dir = body getfilenames dir 
    

    现在,您可以在将 body 插入递归函数之前对其进行包装:

    let wrap f = 
       let d = (* ... *) in
       d, fun recfun x ->
           let ok, res = d.TryGetValue(x)
           if ok then d.[x] <- d.[x] + 1
           else d.Add(x, 1)
           f recfun x 
    
    let calls, counted_body = wrap body
    
    let getfilenames dir = counted_body getfilenames dir
    

    请注意,wrap 函数返回包装函数(具有与原始函数相同的签名)和字典,以供外部访问。然后将在calls 中找到呼叫次数。

    【讨论】:

    • 我被困在 2 点上,注释掉字典,现在应该放什么类型?在 wrap 函数的最后一行 f recfun x 我不明白这是如何工作的:(
    • 字典:和以前一样(唯一的区别是现在 f 的类型是('a -&gt; 'b) -&gt; 'a -&gt; 'b,因为它需要recfun)。 recfun 表示应该由 f 在子目录上调用的函数(因为 f 不再是递归的)。这让您可以使用 counted 函数来处理子目录
    • 让调用,counted_body = wrap body;;错误 FS0030:值限制。值 'counted_body' 已被推断为具有通用类型 val counted_body : ((string -> '_a) -> string -> seq) when '_a :> seq
    • 听起来像 F# 并没有推断出 'b 的类型在 ('a -&gt; 'b) -&gt; 'a -&gt; 'b 中是相同的(只是第一个可以转换为第二个)。您必须手动将f 注释为('a -&gt; 'b) -&gt; 'a -&gt; 'brecfun'a -&gt; 'b
    • 这种技术被称为“解开递归结”。
    【解决方案2】:

    正如 Victor 指出的那样,您不能采用递归函数并将某些行为“注入”到递归调用发生的地方(因为该函数已经完成)。您需要为此提供一些扩展点。在 Victor 的解决方案中,这是通过将要递归调用的函数作为参数来完成的,这是最通用的解决方案。

    一个更简单的选择是使用 F# value recursion,它允许您创建一个函数值并在其声明中使用它。您可以通过调用另一个向函数添加一些行为(例如计数)的函数来使用它来创建递归函数:

    let rec factorial = counted (fun x ->
      if x = 0 then 1 
      else x * (factorial (x - 1)) )
    
    factorial 10
    

    在 lambda 函数内部,我们可以直接访问我们定义的函数,因此不需要将传递函数作为附加参数递归调用。函数 counted 简单地包装了给定函数 f 并添加了一些功能:

    let counted f =
      let count = ref 0
      (fun x -> 
        count := !count + 1;
        printfn "call: %d" (!count)
        f x)
    

    感谢值递归,该功能将被添加到factorial 函数中(因此当它调用自身时,它将调用添加了计数支持的版本)。

    【讨论】:

    • 这更简单,也更容易理解,但 Victors 代码是我试图实现的。
    猜你喜欢
    • 2014-06-08
    • 1970-01-01
    • 2016-08-06
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 2022-11-25
    • 1970-01-01
    • 2019-08-02
    相关资源
    最近更新 更多