【发布时间】: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
我需要使用 foldr 找到列表的最小值。
这是我写的代码:
fun minlist nil = nil
| minlist (x::xs) = List.foldr (fn (y,z) => if y < z then y else z) x xs;
但是我收到一个错误:“重载 > 不能应用于“列表”类型的参数
我被困了一段时间。任何帮助表示赞赏
【问题讨论】:
标签: sml
您的第一个子句说空列表的最小值是一个列表。
因此,(fn (y,z) => if y < z then y else z) 生成一个列表,而y 和z 也必须是列表。
您无法为空列表生成任何合理的值,因此您应该删除这种情况并接受编译警告,或者引发异常。
【讨论】:
您的表达式if y < 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)
您可能会注意到minimum 和foo 之间的相似之处:
acc 是 x,一些最小值bar 是 Int.min。这是对minimum的递归方案进行泛化的尝试。
给定函数foldr:
fun foldr f e [] = e
| foldr f e (x::xs) = f (x, foldr f e xs);
您可能会注意到相同的相似之处:
f 是bar,但是做成了参数e 是acc,但是做成了参数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 以与上面完全相同的方式解决此练习,并且您将拥有一个使用较少的尾递归变体堆栈空间。
【讨论】: