Haskell中如何对列表相邻元素按指定数量分组?
在Haskell中实现滑动窗口分组
嘿,这个滑动窗口分组的需求在Haskell里有好几种实用的实现思路,我给你整理几个常用的:
方法一:手动递归实现(适合理解原理)
这个方法最直观,一步步拆解需求:先判断当前列表长度是否够分组的大小,够的话就取前n个元素作为一组,再递归处理剩下的列表(去掉第一个元素)。
sliding :: Int -> [a] -> [[a]] sliding n xs | length xs < n = [] | otherwise = take n xs : sliding n (tail xs)
不过要注意,length函数需要遍历整个列表,所以如果处理超大型列表的话,这个方法的效率会稍低一些。
方法二:用unfoldr高效实现
Data.List里的unfoldr函数非常适合生成这种迭代式的列表,它可以从初始状态一步步生成结果。我们可以用它来避免不必要的遍历:
import Data.List (unfoldr) sliding' :: Int -> [a] -> [[a]] sliding' n = unfoldr (\xs -> if length xs >= n then Just (take n xs, tail xs) else Nothing)
如果想进一步优化,还可以自己写一个短路的“判断是否有至少n个元素”的函数,避免调用length遍历整个列表:
import Data.List (unfoldr) hasAtLeast :: Int -> [a] -> Bool hasAtLeast 0 _ = True hasAtLeast _ [] = False hasAtLeast n (_:xs) = hasAtLeast (n-1) xs sliding'' :: Int -> [a] -> [[a]] sliding'' n = unfoldr (\xs -> if hasAtLeast n xs then Just (take n xs, tail xs) else Nothing)
这个版本在处理大列表时会更高效,因为hasAtLeast只要遍历到足够的元素就会返回结果,不用走完全程。
方法三:用tails简化实现
Data.List的tails函数可以生成列表的所有后缀,利用它我们可以写出非常简洁的代码:
import Data.List (tails) sliding''' :: Int -> [a] -> [[a]] sliding''' n xs = filter (\window -> length window == n) $ map (take n) (tails xs)
举个例子,tails [1,2,3,4,5,6,7]会生成[[1,2,3,4,5,6,7],[2,3,4,5,6,7],...,[7],[]],我们给每个后缀取前3个元素,再过滤掉长度不足3的结果,就得到了想要的滑动分组。这个写法简洁易懂,日常使用完全足够。
测试一下的话,调用sliding 3 [1,2,3,4,5,6,7](或者其他任意一个实现),都会得到你想要的结果:[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7]]。
内容的提问来源于stack exchange,提问作者Qwertie
相关产品推荐
相关产品推荐

