【问题标题】:Hofstadter Female and Male sequences in SMLSML 中的 Hofstadter 女性和男性序列
【发布时间】:2017-10-08 12:42:30
【问题描述】:

这是我的第一个 SML 程序。我正在尝试编写一个函数,以列表形式将第一个数字返回到 Hofstadter 的女性或男性序列的第 n 个数字。到目前为止我所拥有的是:

val m = fn (n) => if n = 0 then 1 :: [] else m f (n - 1);
val f = fn (n) => if n = 0 then 0 :: [] else f m (n - 1);

您可以在此处了解序列: https://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_Female_and_Male_sequences

我得到的错误是:

[opening sequence.sml]
sequence.sml:1.49 Error: unbound variable or constructor: f
sequence.sml:1.47-1.58 Error: operator is not a function [tycon mismatch]
  operator: int list
  in expression:
    (m <errorvar>) (n - 1)
val it = () : unit

我该如何纠正这个问题?

【问题讨论】:

    标签: sequence sml smlnj ml sequences


    【解决方案1】:

    我最终采用了这种方法:

    fun
        m (n) = if n = 0 then 0 else n - (f (m (n - 1)))
    and
        f (n) = if n = 0 then 1 else n - (m (f (n - 1)));
    val seq = fn n => List.tabulate((n), f);
    

    速度很慢。如果有人有更快的版本,那么我很乐意看到它。

    【讨论】:

      【解决方案2】:

      虽然您已经修复了它们,但您的原始方法存在两个问题:

      1. 函数应用程序在 SML 中是左关联的,因此 m f (n - 1) 被解释为 (m f) (n - 1),而不是所需的 m (f (n - 1))。您可以通过明确指定括号 m (f (n - 1)) 来解决此问题。

      2. 为了能够从m 调用f f 调用m,您需要在第一个声明中使用关键字fun 而不是val(使函数递归),并在第二个声明中使用关键字and 而不是funval(使函数与第一个函数相互递归)。这看起来像

        fun f n = ... (* I can call f or m from here! *)
        and m n = ... (* I can call f or m from here! *)
        

      为了让它更快,你可以记忆!诀窍是让fm 将它们自己的记忆版本作为参数。

      (* Convenience function: Update arr[i] to x, and return x. *)
      fun updateAndReturn arr i x = (Array.update (arr, i, SOME x); x)
      
      (*
       * Look up result of f i in table; if it's not found, calculate f i and
       * store in the table. The token is used so that deeper recursive calls
       * to f can also try to store in the table.
       *)
      fun memo table f token i =
        case Array.sub (table, i)
          of NONE => updateAndReturn table i (f token i)
           | SOME x => x
      
      (*
       * Given f, g, and n : int, returns a tuple (f', g') where f' and g' are memoized
       * versions of f and g, respectively. f' and g' are defined only on the domain
       * [0, n).
       *)
      fun memoizeMutual (f, g) n =
        let
          val fTable = Array.array (n, NONE)
          val gTable = Array.array (n, NONE)
          fun fMemo i = memo fTable f (fMemo, gMemo) i
          and gMemo i = memo gTable g (gMemo, fMemo) i
        in
          (fMemo, gMemo)
        end
      
      fun female _ 0 = 1
        | female (f, m) n = n - m (f (n - 1))
      
      fun male _ 0 = 0
        | male (m, f) n = n - f (m (n - 1))
      
      fun hofstadter upTo =
        let
          val (male', female') = memoizeMutual (male, female) upTo
        in
          (List.tabulate (upTo, male'), List.tabulate (upTo, female'))
        end
      

      我将fm 重命名为femalemale。记忆的fMemogMemo 通过memoizeMutual 穿过femalemale。有趣的是,如果我们调用 male',那么 male'female' 的结果都会被记忆。

      要确认它确实更快,请尝试评估 hofstadter 10000。它比您的版本所花费的永远快得多。

      最后一点,唯一的递归函数是fMemogMemo。我编写的所有其他函数都可以编写为匿名函数(val memoizeMutual = fn ...val female = fn ... 等),但我选择不这样做,因为在 SML 中编写递归函数的语法要紧凑得多。

      为了概括这一点,您可以将记忆的数组版本替换为哈希表之类的东西。这样我们就不必预先指定记忆的大小。

      【讨论】:

      • 非常感谢您发布和解释您的解决方案。 “记忆化”对我来说是一个新的过程。我期待在未来实施它。感谢您的宝贵时间。
      猜你喜欢
      • 1970-01-01
      • 2016-09-02
      • 2013-06-11
      • 1970-01-01
      • 2013-09-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-13
      • 1970-01-01
      相关资源
      最近更新 更多