... 以下代码似乎有效;但是,我无法理解它是如何工作的。
fun delete(x,[]) = []
| delete(x,y::l) = if x=y then delete(x,l) else y::delete(x,l);
fun remove_dup [] = []
| remove_dup (x::l) = x::remove_dup(delete(x,l));
我尝试使用更基本的语法来复制它...
这似乎是一件明智的事情。
正如 molbdnilo 在评论中指出的那样,hd 和 tl 并不是更基本的。
我可以像这样格式化delete:
fun delete (x, []) = []
| delete (x, y :: rest) =
if x = y
then delete (x, rest) (* delete y *)
else y :: delete (x, rest) (* keep y *)
理解这个函数的一种方法是解释它的作用:
-
它需要(x, ...) 的一个元组,其中x 是单个元素,第二部分是此类元素的列表。这里使用了模式匹配,因此列表要么采用[](空列表)或y :: rest(具有至少一个元素y 和其余元素列表rest 的列表)的形式。
-
当列表为空时(匹配[] 模式),结果也为空:从空列表中删除x 只需返回值[] 即可。请注意,在delete (x, []) = [] 中,两个[]s 实际上的含义略有不同:第一个(在= 的左侧)是一个模式,第二个(在@987654338 的右侧) @) 是一个值,一个实际的空列表。
-
当列表不为空时,表示它至少由一个元素y和一个尾部rest组成。当至少有一个元素y时,表示至少有一个候选删除。如果x = y,则“删除”包括返回一个与输入列表非常相似的列表,除了y 没有在任何地方提及。所以在then 分支中,没有提到y,而是只提到了delete (x, rest),这意味着:
结果是从列表的其余部分中删除 x 得到的结果。
这里还要注意,可以对模式y :: ys 和值y :: delete (x, rest) 进行类似的区分。这里:: 是非空列表的值构造函数,它用于创建y 持续存在的新列表值。但该列表的尾部可能已删除了 x 的副本。
理解这个函数的另一种方法是手动运行它。
想象一个像delete (3, [1,3,3,7])这样的电话;我将每行重写一次,这对应于计算量的减少:
(* 1 *) delete (3, [1,3,3,7])
(* 2 *) ~> if 3 = 1 then delete (3, [3,3,7]) else 1 :: delete (3, [3,3,7])
(* 3 *) ~> 1 :: delete (3, [3,3,7])
(* 4 *) ~> 1 :: (if 3 = 3 then delete (3, [3,7]) else 3 :: delete (3, [3,7]))
(* 5 *) ~> 1 :: delete (3, [3,7])
(* 6 *) ~> 1 :: (if 3 = 3 then delete (3, [7]) else 3 :: delete (3, [7]))
(* 7 *) ~> 1 :: delete (3, [7])
(* 8 *) ~> 1 :: (if 3 = 7 then delete (3, []) else 7 :: delete (3, []))
(* 9 *) ~> 1 :: 7 :: delete (3, [])
(* 10 *) ~> 1 :: 7 :: []
-
这是初始函数调用。
-
因为输入列表非空,我们跳过第一个模式匹配并将值(3, [1,3,3,7]) 拟合到模式(x, y :: rest):您可以通过设置x = 3、y = 1 和@987654359 轻松做到这一点@。之后,我们将 delete (3, [1,3,3,7]) 替换为完整的函数体,if ... 替换为变量。
-
由于3 = 1 为假,因此选择了else 分支,因此将整个表达式替换为else 分支中的子部分1 :: delete (3, [3,3,7])。
-
这里发生了一些有趣的事情:
- 由于
(* 3 *)包含1 :: delete (3, [3,3,7]),你有两个半计算结果:一方面你想构造列表1 :: ...,另一方面你不知道尾部delete (3, [3,3,7])然而,因为它还没有被计算出来。
- 所以在计算
1 :: ... 之前,你必须计算delete (3, [3,3,7])。这导致只替换尾部,所以1 :: (if 3 = 3 ...)。除了嵌套表达式之外,减少几乎相同,除了...
-
由于3 = 3 为真,因此选择了then 分支,这仅导致值delete (3, [3,7]) 没有y :: ... 部分,因为y = 3 并且我们正在删除3s,这样做3 :: ... 显然是错误的做法。
事情以这种方式重复(包括y或不包括)直到(* 9 *),其中1 :: 7 :: delete (3, [])要求将3从空列表中删除。这是第一次触发delete (x, []) = []的第一个case,递归停止。
你可以用remove_dup做类似的事情;也就是手跑。
您可能会发现它并不是很简单,因为它包含两个递归函数,一个调用另一个函数并直接提供其输入。不管你怎么扭动这个功能,它都会有点困难。
有一些方法可以让它看起来更容易一些:
fun remove_dup [] = []
| remove_dup (x :: rest) =
let val rest_without_x = delete (x, rest)
in x :: remove_dup(rest_without_x)
end
你至少可以发现
-
remove_dup [] = []:从空列表中删除重复项很容易。
- 当至少有一个元素
x 时,通过执行delete (x, rest) 从rest 中删除所有出现的x。这个列表,我称之为rest_without_x,然后被提供给remove_dup。
- 由于我们只删除了
x 的重复项,因此我们希望将其包含一次,而我们选择的x 出现的一次恰好是我们遇到的第一个。
如果你想像上面的例子一样手动运行它,你可能想运行原始的、紧凑的,因为函数体只跨越一行。它可能看起来像:
remove_dup [1,1,1,2,2,3,1,2,3]
~> 1 :: remove_dup(delete(1, [1,1,2,2,3,1,2,3]))
~> 1 :: remove_dup([2,2,3,2,3])
~> 1 :: 2 :: remove_dup(delete(2, [2,3,2,3]))
~> ...
请注意,我在解析 remove_dup(...) 之前解析了内部表达式 delete(1, ...),因为它的参数尚未解析。 (这是严格评估语义的结果;一些函数式编程语言允许您在没有完全评估其参数的情况下调用函数!)
尝试自己完成其余的工作。