首先让我们不用担心结尾处的短窗口,先看看其他的窗口:
import Data.List (tails)
windows' :: Int -> [a] -> [[a]]
windows' n = map (take n) . tails
> windows' 3 [1..5]
[[1,2,3],[2,3,4],[3,4,5],[4,5],[5],[]]
现在我们想要摆脱长度较短的元素,而不用检查每一个元素的长度。
因为我们知道它们在末尾,所以可以这样去除:
windows n xs = take (length xs - n + 1) (windows' n xs)
但这并不好,因为我们仍然需要额外遍历一次xs来获取其长度。它也无法处理无限列表,而你原来的解决方案可以。
相反,我们可以编写一个函数,使用一个列表作为标尺来测量从另一个列表中取出的数量:
takeLengthOf :: [a] -> [b] -> [b]
takeLengthOf = zipWith (flip const)
> takeLengthOf ["elements", "get", "ignored"] [1..10]
[1,2,3]
现在我们可以写成这样:
windows :: Int -> [a] -> [[a]]
windows n xs = takeLengthOf (drop (n-1) xs) (windows' n xs)
> windows 3 [1..5]
[[1,2,3],[2,3,4],[3,4,5]]
同样适用于无限列表:
> take 5 (windows 3 [1..])
[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7]]
正如Gabriella Gonzalez所说,如果你想使用整个结果,时间复杂度并不会更好。但是如果你只使用其中的一些窗口,我们现在成功避免了对未使用的窗口执行take
和length
操作。
foldr (zipWith (:)) (repeat []) . take m . tails
。 意思是将一个列表分成长度为m的子列表,并将每个子列表转置成一个新的列表,其中使用了函数式编程中的高阶函数foldr
、zipWith
和tails
。 - Will NessZipList
上的sequenceA
所做的事情。 :) (我所说的“或”是指“或者可以明确地写成...”)。sequenceA
==foldr ((<*>).((:)<$>)) (pure [])
。 - Will Ness