Haskell组列表直到组满足谓词

时间:2014-01-13 21:38:28

标签: list haskell

如何对列表进行分组,以便在给定谓词不再成立时每个组都“关闭”。像

这样的东西
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函数似乎都不会执行此类操作。谓词似乎总是应用于源列表元素。

4 个答案:

答案 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))

我很确定调用initstails会遍历列表两次,但您可以轻松融合这两个函数:

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))中有所帮助,以这种方式建立一个反向列表。然后,当我们完成当前的块(不再ysp 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