标准 ML: 计算给定集合的平均值

4
我最近接到了一个任务,需要在标准ML中计算一组实数的平均值(由输入给出)。
以下是一个函数的想法,您可以输入一个实数列表并接收这些数字的平均值(也是实数),当您输入该函数时,终端会将其作为返回答案给出:
average = fn : real list -> real

我们在教程中已经讨论过这个问题,但我想知道在标准ML中创建这样的函数是否有什么诀窍。
提前感谢!
2个回答

5
将数字相加并除以长度。简单的递归sum通常是任何SML教程中您会看到的第一个示例。您需要将sum的空列表基础情况评估为0.0而不是0,以确保返回类型为real。一旦定义了sum函数,就可以使用sum和内置的length函数在1行中定义average。一个微妙之处是SML不允许将实数除以整数。在将总和除以长度之前,您可以在长度上使用转换函数Real.fromInt。在传递相同的列表两次(一次求和,一次计算长度)方面存在一些低效性,但是当您首次学习语言时,没有理由担心这些问题。
编辑:由于您已经找到了自然的解决方案并在评论中分享了它,这里是一个更习惯用法的版本,它在列表上进行了一次平均值计算:
fun average nums =
    let
        fun av (s,n,[]) = s/Real.fromInt(n)
        |   av (s,n,x::xs) = av (s+x,n+1,xs)
    in
        av (0.0, 0, nums)
    end;

它的工作原理是定义一个助手函数来完成繁重的工作。这在函数式编程中被广泛使用。在没有可变状态的情况下,一种常见的技巧是将数量明确地作为参数传递,这些数量将在相应循环中被依次修改。这些参数通常称为累加器,因为它们通常累积增长的列表、运行总和、运行乘积等。这里的 s 和 n 是累加器,其中 s 是元素的和,n 是列表的长度。在基础情况下,即 (s,n,[]),没有更多的累积内容,因此返回最终答案。在非基础情况下,即 (s,n,x::xs),s 和 n 会被适当地修改,并与列表的尾部一起传递给助手函数。av 的定义是tail-recursive,因此可以像循环一样快速运行而不会增加堆栈。整个 average 函数需要做的唯一事情就是使用适当的初始值调用助手函数。let ... helper def ... in ... helper called with start-up values ...end 是一种常见的习惯用法,用于防止程序的顶层被助手函数淹没。

给学生提供指导/解释而不是仅仅给出一个没有教授任何东西的答案,这是一个A+的表现。 - Mulan
@john 感谢你的帮助!在教程中,我们讨论了使用几个函数来完成此操作。我们不应该使用任何内置库(例如Real.fromInt)。因此,我尝试使用多个函数方法,并得到了类似于以下内容的结果(可以工作):求和函数: fun sum(h::t)=h + sum(t) | sum(nil)=0.0;长度函数: fun length(h::t) = 1.0 + length(t) | length(nil) = 0.0;完整函数: fun average(l) = sum(l) / length(l); - opheliaxo
@opheliaxo 很好。我觉得在递归情况之前放置基本情况会更自然一些,但这主要是个人口味和惯例的问题。由于我不再担心在别人自己解决问题之前帮他们完成作业,所以我编辑了我的答案,包括我认为在 SML 中计算平均值最惯用和高效的方法。 - John Coleman

1

由于只有非空列表才能有平均值,因此 John Coleman 的回答的另一种选择是:

fun average [] = NONE
  | average nums =
    let
        fun av (s,n,[]) = s/Real.fromInt(n)
          | av (s,n,x::xs) = av (s+x,n+1,xs)
    in
        SOME (av (0.0, 0, nums))
    end;

计算平均数的函数是否应该考虑非空列表,取决于您是否打算将其导出或仅在范围内使用,在此范围内,您可以保证输入列表是非空的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接