如何对列表进行分组,以便在给定谓词不再成立时每个组都“关闭”。像
这样的东西groupWhile :: [Int] -> ([Int]->Bool) -> [[Int]]
这样
groupWhile [1,2,3,5,7,1,3,5,7] (\xs -> (<=4) $ length $ filter odd xs)
= [[1,2,3,5,7],[1,3,5,7]]
我需要这个用于纸牌游戏,我想把事件分成几轮,这样每一轮都有四张牌。然而,并非所有事件都是“卡片”事件(上例中的偶数)。
Data.List和Data.List.Split函数似乎都不会执行此类操作。谓词似乎总是应用于源列表元素。
答案 0 :(得分:3)
这是最基本的,我们有
groupWhile as pred = collect [] as where
collect acc [] = reverse acc
collect acc (x:xs) =
let acc' = x:acc
racc' = reverse acc'
in if pred racc'
then racc' : collect [] xs
else collect acc' xs
虽然这确实有很多逆转。我们可以使用Data.List
中的两个函数直接计算它。
import Data.List (inits, tails)
-- forall finite (ls :: [a]) . length (inits ls) == length (tails ls)
groupWhile :: [a] -> ([a] -> Bool) -> [[a]]
groupWhile as p = pop (inits as) (tails as) where
pop [] [] = []
pop (initAs:is) (tailAs:ts)
| p initAs = initAs : groupWhile tailAs p
| otherwise = pop is ts
此处inits
构建可能的组列表,按包含顺序排列,tails
构建“延续”,列表的其余部分提供我们选择当前组。它们是一起制作的,每当我们在我们的成果中成功找到一个新成功的小组时,我们就会继续重新审视“当前的延续”tailAs
。否则,我们会重复pop
函数。
答案 1 :(得分:2)
将其简化为基础时非常简单:按长度按升序扫描给定列表的子列表,并返回满足谓词的最长列表。然后只是简单地说明了什么。
groupWhile _ [] = []
groupWhile pred xs = let (a,b) = splitAtFunc pred xs in a : groupWhile pred b
splitAtFunc f xs = last $ filter (f . fst) (zip (inits xs) (tails xs))
我很确定调用inits
和tails
会遍历列表两次,但您可以轻松融合这两个函数:
initsAndTails xs = ([], xs) : case xs of
[] -> []
x : xs' -> map (\(a,b) -> (x:a, b)) $ initsAndTails xs'
答案 2 :(得分:1)
可能有更优雅的解决方案,但这是一种简单的方法:
groupWhile :: [a] -> ([a] -> Bool) -> [[a]]
groupWhile ys p = go [] p ys
where
go xs _ [] = [reverse xs]
go xs p (y:ys) | p (y:xs) = go (y:xs) p ys
go xs p (y:ys) = reverse xs : go [y] p ys
注意:因为snocing到列表的末尾(xs ++ [y]
)效率非常低(O(n)
),我们在go
的第二种情况((y:xs)
)中有所帮助,以这种方式建立一个反向列表。然后,当我们完成当前的块(不再ys
或p xs
为假)时,我们再次反转xs以使它们按正确的顺序排列。
答案 3 :(得分:1)
最简单的方法是使用sicp中推荐的线性递归方法,即将您的状态(在本例中为“当前子列表”和“当前的子列表列表”分解为尾部的参数) -call递归辅助函数。
这个简单的解决方案要求p对于反转列表不敏感。
groupWhile :: [Int] -> ([Int]->Bool) -> [[Int]]
groupWhile = groupWhileHelp [] []
where groupWhileHelp a [] [] _ = reverse a
groupWhileHelp a b [] _ = reverse $ reverse b : a
groupWhileHelp a b (x:xs) p | p $ x : b = groupWhileHelp a (x:b) xs p
groupWhileHelp a b (x:xs) p = groupWhileHelp (reverse b : a) [x] xs p
另一种简单,经典的方法是使用foldl
(每次都会反转列表,在增加计算时间的同时消除对p的限制)。当然,每次递归方案原则上都可以反转列表。
groupWhile :: [Int] -> ([Int]->Bool) -> [[Int]]
groupWhile l p = reverse $ foldl (addOne p) [] l where
addOne _ [] x = [[x]]
addOne p (xs:xss) x | p $ xs ++ [x] = (xs ++ [x]):xss
| otherwise = [x]:xs:xss