【问题标题】:SML: function with multiple outputsSML:具有多个输出的函数
【发布时间】:2013-02-06 18:18:25
【问题描述】:

我是 SML 的新手,我想更新我的函数,使其有两个输出:一个列表 AND 1 或 0。这里提出了该函数:SML: Remove the entry from the List。它返回一个更新的列表,其中没有包含“elem”的行。

fun removeElem elem myList = filter (fn x => x <> elem) myList

如果原始数据已被删除,该函数应返回一个新列表 AND 1。否则,它应该返回一个旧列表 AND 0。

高度赞赏任何提示或示例。谢谢。

【问题讨论】:

  • 不,这不是家庭作业。我只是在学习 SML。因此,我不要求为我编写一个完整的函数。我想看一个具有多个输出的类似功能的示例。谢谢。
  • @KlausosKlausos:这基本上就是这里的[homework] 标签的意思,所以你可能想在将来使用它。它告诉我们目标是学习,所以我们不应该给你一个完整的解决方案,而是解释和提示并尝试帮助你理解解决方案

标签: list sml


【解决方案1】:

请注意,所有 SML 函数采用单个输入并返回单个输出。相反,请考虑返回一个包含新列表的元组和一个指示是否删除了任何元素的标志。一种可能性是使用标准基础中的几个函数来测试elem 是否在myList 中,并建立一个由它和问题中显示的filter 的结果组成的元组。测试可能如下所示:

Option.isSome (List.find (fn x => x = elem) myList)

有更简洁的写法,但它显示了这个想法。请注意,它返回 bool 而不是 int;这更精确,所以我不会转换为问题中要求的整数。

上面的一个缺点是它需要遍历列表两次。为避免这种情况,请考虑函数必须返回的 type:不带 elem 的列表元组和显示是否已删除任何 elems 的标志。然后我们可以编写一个函数,它接受一个新值和一个(有效)元组,并返回一个有效元组。一种可能性:

fun update(x, (acc, flag)) = if x = elem then (acc, true) else (x :: acc, flag)  

然后我们可以将update 逐个应用于myList 的每个元素。由于我们希望列表的顺序保持不变,除了删除的元素外,我们应该从右到左遍历myList,将结果累积到一个初始为空的列表中。函数foldr 将直接执行此操作:

foldr update ([], false) myList

但是,foldr 高阶函数中隐藏着很多逻辑。

要将其用作学习练习,我建议使用此问题以几种方式实现该功能:

  • 作为递归函数
  • 作为尾递归函数
  • 使用高阶函数foldlfoldr

了解这些版本之间的差异将有助于了解 SML 的工作原理。对于每个版本,让类型指导您。

【讨论】:

    【解决方案2】:

    正如您之前的一些问题所述;返回 0 或 1 作为所发生情况的指示符是一个非常糟糕的设计,因为您无法从类型中得到任何保证,无论您是否会得到 -42 作为结果。由于您使用的是强类型语言,因此您不妨利用它来发挥自己的优势:

    1. 最明显的做法是返回一个布尔值,因为这实际上是您使用 0 和 1 模拟的结果。在这种情况下,您可以返回 (true, modified_list)(false, original_list) 对。
    2. 既然您想将一些数据与结果相关联,还有另一件——也许,对某些人来说,不太明显——要做的事情;将结果作为选项返回,将列表中的更改指示为SOME modified_list,并将无更改指示为NONE

    在任何一种情况下,您都必须“记住”您是否确实从原始列表中删除了任何元素,因此您不能使用filter 函数。相反,您必须自己使用与您最初发布的代码相同的代码来执行此操作。

    一种方法是这样的

    fun removeElem _ [] = (false, [])
      | removeElem elem (x::xs) =
        let
          val (b, xs') = removeElem elem xs
        in
          if elem = x then
            (true, xs')
          else
            (b, x::xs')
        end
    

    另一种方法是使用累加器参数来存储结果列表

    fun removeElem elem xs =
        let
          fun removeElem' [] true res = SOME (rev res)
            | removeElem' [] false _ = NONE
            | removeElem' (x::xs) b res =
              if elem = x then
                removeElem' xs true res
              else
                removeElem' xs b (x::res)
        in
          removeElem' xs false []
        end
    

    由于解决方案是以相反的顺序构建的,因此我们在返回结果之前反转结果。这确保我们在向结果列表中添加元素时不必使用代价高昂的追加操作:res @ [x]

    【讨论】:

    • 我发现“你不能用非尾递归 [原文] 函数构建结果列表”这句话不清楚,甚至具有误导性。您可以将累积参数添加到您的 fooFilter 而不使其尾递归。我真的不确定你在说什么,也许你可以澄清一下?
    • 我看不出它是如何不清楚的,如果你包括关于为什么不可能的假设:即他想要返回指标 列表,正如我在你引用的同一句话中所说的(但忽略了包括在内)。添加 accum 参数肯定会使 fooFilter 尾部递归(除非您故意在递归调用周围添加不​​需要的表达式)。
    • 不,fooFilter 不会自动变成尾递归。你可以想象得到一个带有列表尾部的递归调用的结果,然后从中建立一个新的结果。这可能具有相同的修改签名,不会在尾部位置调用fooFilter,并且仅在堆栈空间方面是不合理的。
    • 嗯。不太确定这是否是你所得到的,但我修复了另一个(更好的)示例,它仍然是尾递归而不使用 accumulator 参数。
    • 第一个版本正是我所得到的:if 发生在对removeElem 的调用之后,所以removeElem 不在尾部位置。感谢您努力澄清这一点。
    猜你喜欢
    • 1970-01-01
    • 2020-04-09
    • 2012-02-01
    • 1970-01-01
    • 2015-12-04
    • 2013-05-02
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多