这是部分point-free 一行 Haskell 解决方案:
ma p = reverse . map ((/ (fromIntegral p)) . sum . take p) . (drop p) . reverse . 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”条目(此处将 p 视为 2):
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]]
如果您不熟悉 (.) dot/nipple 符号,它是“功能组合”的运算符,这意味着它将一个函数的输出作为另一个函数的输入传递,将它们“组合”成一个函数。 (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”不会在物理上颠倒列表的顺序,它只是将其放在堆栈上(好 ol'lazy 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 遍历列表时,它会从前一个 "mapping" 传递到下一个 "mapping"。我们使用累加器作为移动平均线,并且由于我们的列表由刚刚离开滑动窗口的元素和刚刚进入的元素(我们刚刚压缩的列表)组成,我们的滑动函数采用第一个数字“x”远离平均值并添加第二个数字“y”。然后我们传递新的's'并返回's'除以'p'。 "snd" (second) 只取一对(元组)的第二个成员,用于取 mapAccumL 的第二个返回值,因为 mapAccumL 将返回累加器以及映射列表。
对于那些不熟悉$ symbol 的人来说,它是“应用程序运算符”。它实际上并没有做任何事情,但它具有“低右关联绑定优先级”,因此这意味着您可以省略括号(注意 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”来编译任一解决方案。