这里是一个部分使用 point-free 的一行 Haskell 解决方案:
ma p = reverse . map ((/ (fromIntegral p)) . sum . take p) . (drop p) . reverse . tails
首先,它将 tails 应用于列表以获取“tails”列表,因此:
Prelude List> tails [2.0, 4.0, 7.0, 6.0, 3.0]
[[2.0,4.0,7.0,6.0,3.0],[4.0,7.0,6.0,3.0],[7.0,6.0,3.0],[6.0,3.0],[3.0],[]]
将其反转并删除前面的 'p' 个条目(这里以2为p):
Prelude List> (drop 2 . reverse . tails) [2.0, 4.0, 7.0, 6.0, 3.0]
[[6.0,3.0],[7.0,6.0,3.0],[4.0,7.0,6.0,3.0],[2.0,4.0,7.0,6.0,3.0]]
如果您不熟悉(.)点/乳头符号,它是“函数合成”的运算符,意味着它将一个函数的输出作为另一个函数的输入进行传递,将它们“组合”成一个单一的函数。 (g . f) 的意思是“对一个值运行f,然后将输出传递给g”,因此 ((f . g) x) 与 (g(f x)) 相同。通常使用这个符号可以带来更清晰的编程风格。
然后,它将函数 ((/ (fromIntegral p)) . sum . take p) 映射到列表上。因此,对于列表中的每个列表,它取前 'p' 个元素,求和,然后除以 'p'。然后我们用 "reverse" 翻转列表即可。
Prelude List> map ((/ (fromIntegral 2)) . sum . take 2) [[6.0,3.0],[7.0,6.0,3.0]
,[4.0,7.0,6.0,3.0],[2.0,4.0,7.0,6.0,3.0]]
[4.5,6.5,5.5,3.0]
这看起来比实际情况要低效得多;“reverse”在列表被评估之前并不会物理上反转列表的顺序,它只是将其放置到堆栈上(好老的惰性Haskell)。 “tails”也不会创建所有这些单独的列表,它只是引用原始列表的不同部分。 它仍然不是一个很好的解决方案,但只有一行 :)
这里有一个稍微好一点但更长的解决方案,它使用mapAccum进行滑动减法和加法:
ma p l = snd $ mapAccumL ma' a l'
where
(h, t) = splitAt p l
a = sum h
l' = (0, 0) : (zip l t)
ma' s (x, y) = let s' = (s - x) + y in (s', s' / (fromIntegral p))
首先,我们将列表在“p”处分成两部分:
Prelude List> splitAt 2 [2.0, 4.0, 7.0, 6.0, 3.0]
([2.0,4.0],[7.0,6.0,3.0])
求第一位的和:
Prelude List> sum [2.0, 4.0]
6.0
将第二个位与原始列表压缩在一起(这只是按顺序配对两个列表中的项目)。原始列表显然更长,但我们失去了这个额外的位:
Prelude List> zip [2.0, 4.0, 7.0, 6.0, 3.0] [7.0,6.0,3.0]
[(2.0,7.0),(4.0,6.0),(7.0,3.0)]
现在我们为mapAccum(ulator)定义一个函数。
mapAccumL与“map”相同,但具有额外的运行状态/累加器参数,该参数从前一个“映射”传递到下一个映射,因为map通过列表。我们使用累加器作为我们的移动平均值,并且由刚刚离开滑动窗口的元素和刚刚进入它的元素(我们刚刚压缩的列表)组成的列表中,我们的滑动函数将第一个数字'x'从平均值中减去并添加第二个数字'y'。然后,我们将新的's'传递并返回's'除以'p'。"snd"(第二个)只取一对(元组)的第二个成员,用于获取mapAccumL的第二个返回值,因为mapAccumL将返回累加器以及映射的列表。
对于那些不熟悉
$符号的人,它是“应用程序运算符”。 它实际上并没有做什么,但它具有“低,右结合绑定优先级”,这意味着您可以省略括号(请注意LISPers),即(f x)与f $ x相同。
运行(ma 4 [2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0])会产生任一解决方案的[4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 6.5]。
哦,你需要导入模块“List”才能编译任一解决方案。