【问题标题】:SML, Using foldr to define min of a listSML,使用 foldr 定义列表的最小值
【发布时间】:2019-03-06 20:33:10
【问题描述】:

我需要使用 foldr 找到列表的最小值。

这是我写的代码:

fun minlist nil = nil
  | minlist (x::xs) = List.foldr (fn (y,z) => if y < z then y else z) x xs;

但是我收到一个错误:“重载 > 不能应用于“列表”类型的参数

我被困了一段时间。任何帮助表示赞赏

【问题讨论】:

    标签: sml


    【解决方案1】:

    您的第一个子句说空列表的最小值是一个列表。
    因此,(fn (y,z) =&gt; if y &lt; z then y else z) 生成一个列表,而yz 也必须是列表。

    您无法为空列表生成任何合理的值,因此您应该删除这种情况并接受编译警告,或者引发异常。

    【讨论】:

      【解决方案2】:

      求两个值中的最小值

      您的表达式if y &lt; z then y else z 有一个内置名称Int.min (y, z)

      处理空列表

      您将空列表处理为minlist nil = nil,这意味着“空 int 列表的最小 int 是空列表”。但是空列表不是 int,因此不能是 int 列表中的元素,也不能是返回最小整数的函数的返回值。

      正如 molbdnilo 所说,您可以忍受编译警告(如果您曾经为函数提供一个空列表,则可能会在运行时引发 Match 异常),或者引发特定异常,例如 Empty给定空列表。两者都不好,但后者至少让问题变得清晰。

      在没有foldr 的情况下编写它可能看起来像:

      fun minimum [] = raise Empty
        | minimum [x] = x
        | minimum (x::xs) = Int.min (x, minimum xs)
      

      将递归函数转换为折叠

      给定一些递归函数foo,它依赖于一些函数bar和一些默认值acc

      fun foo [] = acc
        | foo (x::xs) = bar (x, foo xs)
      

      您可能会注意到minimumfoo 之间的相似之处:

      • accx,一些最小值
      • barInt.min

      这是对minimum的递归方案进行泛化的尝试。

      给定函数foldr

      fun foldr f e []      = e
        | foldr f e (x::xs) = f (x, foldr f e xs);
      

      您可能会注意到相同的相似之处:

      • fbar,但是做成了参数
      • eacc,但是做成了参数

      minimum 中唯一不适合这种通用递归方案的就是处理空列表。所以你仍然需要与foldr分开:

      fun minimum [] = ...
        | minimum (x::xs) = foldr ...
      

      但其他的都差不多。

      错误感知返回类型

      第三种选择是将函数的类型签名更改为

      val minimum : int list -> int option
      

      您当前的练习可能不允许这样做。

      在没有foldr 的情况下编写它可能看起来像:

      fun minimum [] = NONE
        | minimum [x] = SOME x
        | minimum (x::xs) =
          case minimum xs of
               NONE => SOME x
             | SOME y => SOME (Int.min (x, y))
      

      或者更好:

      fun minimum [] = NONE
        | minimum [x] = SOME x
        | minimum (x::xs) = Option.map (fn y => Int.min (x, y)) (minimum xs)
      

      将此函数转换为使用foldr 是相同的过程,但使用不同的f

      尾递归

      没有折叠的minimum 函数(从上面重复):

      fun minimum [] = raise Empty
        | minimum [x] = x
        | minimum (x::xs) = Int.min (x, minimum xs)
      

      有一个问题是它主要使用stack memory

      这可以通过手动评估函数来说明:

         minimum [1,2,3,4,5]
      ~> Int.min (1, minimum [2,3,4,5])
      ~> Int.min (1, Int.min (2, minimum [3,4,5]))
      ~> Int.min (1, Int.min (2, Int.min (3, minimum [4,5])))
      ~> Int.min (1, Int.min (2, Int.min (3, Int.min (4, minimum [5]))))
      ~> Int.min (1, Int.min (2, Int.min (3, Int.min (4, 5))))
      ~> Int.min (1, Int.min (2, Int.min (3, 4)))
      ~> Int.min (1, Int.min (2, 3))
      ~> Int.min (1, 2)
      ~> 1
      

      由于在递归调用返回之前无法计算外部Int.min,因此用于计算函数的堆栈内存量与列表长度成正比增长。

      您可以通过使用累积参数来避免这种情况:

      fun minimum [] = raise Empty
        | minimum (y::ys) =
          let fun helper [] acc = acc
                | helper (x::xs) acc = helper xs (Int.min (x, acc))
          in helper ys y end
      

      手动评估这个函数:

         minimum [1,2,3,4,5]
      ~> helper [2,3,4,5] 1
      ~> helper [3,4,5] (Int.min (2, 1))
      ~> helper [3,4,5] 1
      ~> helper [4,5] (Int.min (3, 1))
      ~> helper [4,5] 1
      ~> helper [5] (Int.min (4, 1))
      ~> helper [5] 1
      ~> helper [] (Int.min (5, 1))
      ~> helper [] 1
      ~> 1
      

      由于Int.min 是可交换的,您不妨使用foldl 而不是foldr 以与上面完全相同的方式解决此练习,并且您将拥有一个使用较少的尾递归变体堆栈空间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-04-06
        • 2023-04-02
        • 2021-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-01
        • 2016-02-02
        相关资源
        最近更新 更多