PureScript - 计算数字数组的平均值
PureScript - Calculate the mean of an array of numbers
考虑以下 PureScript 代码
-- returns the mean value of the numbers in an array
mean :: Array Number -> Number
mean numbers =
-- code goes here
如何编写这个函数?
JavaScript 中的等价物如下所示:
function mean (numbers) {
let g = 0;
for (let n of numbers) {
g += n;
}
return g / numbers.length;
}
但我不知道如何在 mean 函数中创建局部变量 g
或者是否有一些不同的、更面向 PureScript 的方法。
如何在 PureScript 中找到一组数字的平均值?
虽然从技术上讲,您可以在 PureScript 中使用变异,但它既笨拙又不舒服,这是故意的:PureScript 故意让它变得困难,以阻止您使用它。突变是错误的来源。现在是现代 goto
。尽可能避免。
而是使用函数转换。
例如,平均值是所有数字的总和除以它们的数量。所以就写下来吧:
mean numbers = sum numbers / toNumber (length numbers)
(当然这对于空数组会失败,让我们暂时忽略这个事实)
或者您实际上是在问如何计算总和?或者,更狭义地说,如何在 JavaScript 示例中执行与 for
循环等效的操作?
迭代的最终基础是递归。每一步的递归函数检查是否需要继续迭代。如果有,它会再次调用自己,如果没有,它不会。
Singly-linked 列表(存在于所有 FP 语言中)迭代起来很愉快,因为它们的结构本身反映了递归计算。不幸的是,在 PureScript 中我们必须(默认情况下)处理数组。数组没有令人愉快的递归结构,但您仍然可以使用索引作为迭代媒介进行迭代:
sum :: Array Number -> Int -> Number
sum arr startAt =
case arr !! startAt of
Just x -> x + sum arr (startAt + 1)
Nothing -> 0
在这里,在每次迭代中,我都在检查当前索引是否仍在数组中,同时获取该索引处的元素。如果是,说明我需要继续迭代,所以我递归调用自己。如果不是,说明我完了,我不叫自己了
当然,这里的一个不便之处是此类函数的消费者必须传递一个额外的参数startAt
。这可以通过将实际的递归函数隐藏在外观后面来很好地处理:
sum :: Array Number -> Number
sum arr = go 0
where
go startAt =
case arr !! startAt of
Just x -> x + go (startAt + 1)
Nothing -> 0
使用此类功能需要注意的一件事是它不是 stack-safe。它必须执行与数组中的元素一样多的嵌套调用,所以如果太多,它会炸毁堆栈。
为了解决这个问题,任何递归算法都可以变成所谓的尾递归。这个术语意味着以这样一种方式编写递归函数,即每次它调用自身时,它不会对 return 值做任何事情,除了直接 return 之外。这样写就可以在不消耗栈的情况下执行递归了。
对于上面的示例,将其转换为尾递归的方法是传递“到目前为止的总和”以及当前索引:
sum :: Array Number -> Number
sum arr = go 0 0.0
where
go startIndex sumSoFar =
case arr !! startIndex of
Just x -> go (startIndex + 1) (sumSoFar + x)
Nothing -> sumSoFar
但实际上显式递归并不经常使用。我不会说“从不”甚至“很少”,但这不是 go-to。相反,我们通常使用其他更简单的函数,这些函数本身是建立在递归基础上的。
一个示例位于此答案的最顶部 - 来自 Data.Foldable
的 sum
。但是最通用的函数是 fold
(有两种变体 - foldr
and foldl
)
,它抓住了迭代本身的本质
使用foldl
,sum
函数可以这样写:
sum :: Array Number -> Number
sum arr = foldl (\x sumSoFar -> x + sumSoFar) 0 arr
请注意 foldl
如何为我完成整个迭代部分,我只需要提供过程的“核心”——即如何将“当前元素”x
与“到目前为止积累的状态”sumSoFar
.
最后,您可能会注意到 \x y -> x + y
等同于 (+)
并将其重写得更短:
sum :: Array Number -> Number
sum arr = foldl (+) 0 arr
使用 Data.Foldable
中的 sum
:
import Data.Foldable
import Data.Int (toNumber)
mean :: Array Number -> Number
mean x = sum x / toNumber (length x)
y = mean [1.0, 2.0, 3.0]
或者,使用同一模块中的 foldl
,这暗示了如何编写更通用的代码:
import Data.Foldable
import Data.Int (toNumber)
mean :: Array Number -> Number
mean x = foldl (+) 0.0 x / toNumber (length x)
y = mean [1.0, 2.0, 3.0]
考虑以下 PureScript 代码
-- returns the mean value of the numbers in an array
mean :: Array Number -> Number
mean numbers =
-- code goes here
如何编写这个函数?
JavaScript 中的等价物如下所示:
function mean (numbers) {
let g = 0;
for (let n of numbers) {
g += n;
}
return g / numbers.length;
}
但我不知道如何在 mean 函数中创建局部变量 g
或者是否有一些不同的、更面向 PureScript 的方法。
如何在 PureScript 中找到一组数字的平均值?
虽然从技术上讲,您可以在 PureScript 中使用变异,但它既笨拙又不舒服,这是故意的:PureScript 故意让它变得困难,以阻止您使用它。突变是错误的来源。现在是现代 goto
。尽可能避免。
而是使用函数转换。
例如,平均值是所有数字的总和除以它们的数量。所以就写下来吧:
mean numbers = sum numbers / toNumber (length numbers)
(当然这对于空数组会失败,让我们暂时忽略这个事实)
或者您实际上是在问如何计算总和?或者,更狭义地说,如何在 JavaScript 示例中执行与 for
循环等效的操作?
迭代的最终基础是递归。每一步的递归函数检查是否需要继续迭代。如果有,它会再次调用自己,如果没有,它不会。
Singly-linked 列表(存在于所有 FP 语言中)迭代起来很愉快,因为它们的结构本身反映了递归计算。不幸的是,在 PureScript 中我们必须(默认情况下)处理数组。数组没有令人愉快的递归结构,但您仍然可以使用索引作为迭代媒介进行迭代:
sum :: Array Number -> Int -> Number
sum arr startAt =
case arr !! startAt of
Just x -> x + sum arr (startAt + 1)
Nothing -> 0
在这里,在每次迭代中,我都在检查当前索引是否仍在数组中,同时获取该索引处的元素。如果是,说明我需要继续迭代,所以我递归调用自己。如果不是,说明我完了,我不叫自己了
当然,这里的一个不便之处是此类函数的消费者必须传递一个额外的参数startAt
。这可以通过将实际的递归函数隐藏在外观后面来很好地处理:
sum :: Array Number -> Number
sum arr = go 0
where
go startAt =
case arr !! startAt of
Just x -> x + go (startAt + 1)
Nothing -> 0
使用此类功能需要注意的一件事是它不是 stack-safe。它必须执行与数组中的元素一样多的嵌套调用,所以如果太多,它会炸毁堆栈。
为了解决这个问题,任何递归算法都可以变成所谓的尾递归。这个术语意味着以这样一种方式编写递归函数,即每次它调用自身时,它不会对 return 值做任何事情,除了直接 return 之外。这样写就可以在不消耗栈的情况下执行递归了。
对于上面的示例,将其转换为尾递归的方法是传递“到目前为止的总和”以及当前索引:
sum :: Array Number -> Number
sum arr = go 0 0.0
where
go startIndex sumSoFar =
case arr !! startIndex of
Just x -> go (startIndex + 1) (sumSoFar + x)
Nothing -> sumSoFar
但实际上显式递归并不经常使用。我不会说“从不”甚至“很少”,但这不是 go-to。相反,我们通常使用其他更简单的函数,这些函数本身是建立在递归基础上的。
一个示例位于此答案的最顶部 - 来自 Data.Foldable
的 sum
。但是最通用的函数是 fold
(有两种变体 - foldr
and foldl
)
使用foldl
,sum
函数可以这样写:
sum :: Array Number -> Number
sum arr = foldl (\x sumSoFar -> x + sumSoFar) 0 arr
请注意 foldl
如何为我完成整个迭代部分,我只需要提供过程的“核心”——即如何将“当前元素”x
与“到目前为止积累的状态”sumSoFar
.
最后,您可能会注意到 \x y -> x + y
等同于 (+)
并将其重写得更短:
sum :: Array Number -> Number
sum arr = foldl (+) 0 arr
使用 Data.Foldable
中的 sum
:
import Data.Foldable
import Data.Int (toNumber)
mean :: Array Number -> Number
mean x = sum x / toNumber (length x)
y = mean [1.0, 2.0, 3.0]
或者,使用同一模块中的 foldl
,这暗示了如何编写更通用的代码:
import Data.Foldable
import Data.Int (toNumber)
mean :: Array Number -> Number
mean x = foldl (+) 0.0 x / toNumber (length x)
y = mean [1.0, 2.0, 3.0]