一个更复杂的群体

时间:2012-11-22 23:15:50

标签: list haskell grouping

我想以某种方式对列表进行分组,每个组都尽可能大,并且最多包含n个不同的值(分组是贪婪的)。

例如:groupN 2 [2,2,3,4,5,5,4,3,4,5]应为[[2,2,3],[4,5,5,4],[3,4],[5]]groupN 3 [2,2,3,4,5,5,4,3,4,5]应为[[2,2,3,4],[5,5,4,3,4,5]]group = groupN 1

我还没有想出一个很好的方法来实现它。你呢?解决方案应该尽可能通用,因为我需要对组进行更多的条件。

2 个答案:

答案 0 :(得分:4)

您可以通过定义辅助函数来执行此操作,该函数从列表的头部获取相应的部分。像

这样的东西
splitNDistinct :: (Eq a) => Int -> [a] -> ([a],[a])
splitNDistinct n xs = go 0 [] xs
   where 
     go _ _ [] = ([], [])
     go count seen xs'@(x:xs)
      | x `elem` seen = let (taken, rest) = go count seen xs in (x:taken, rest)
      | count /= n = let (taken, rest) = go (count+1) (x:seen) xs in (x:taken, rest)   
      | otherwise = ([], xs')

这给出了

> splitNDistinct 1 [1, 1,2, 1,2,3, 1,2,3,4]
([1,1],[2,1,2,3,1,2,3,4])
> splitNDistinct 2 [1, 1,2, 1,2,3, 1,2,3,4]
([1,1,2,1,2],[3,1,2,3,4])
> splitNDistinct 3 [1, 1,2, 1,2,3, 1,2,3,4]
([1,1,2,1,2,3,1,2,3],[4])
> splitNDistinct 4 [1, 1,2, 1,2,3, 1,2,3,4]
([1,1,2,1,2,3,1,2,3,4],[])

上面的函数记录了它之前看到的元素数量和数量,然后只有新元素才能看到它,或者是否有新元素的空间。

(通过认识到go的两个递归情况具有几乎相同的结构,除了递归调用中值countseen之间的差异之外,可以通过以上方式加以说明因此,分解可能很容易使功能不那么干净。)

groupN可以通过重复应用splitNDistinct来实现。


考虑到这一点,可以定义mapFst f (a,b) = (f a, b)并在let的递归调用中分别用gomapFst (x:) $ go count seen xs替换mapFst (x:) $ go (count+1) (x:seen) xs - 表达式,这让他们的相似性更加烦人。

答案 1 :(得分:2)

编辑: 正如dbaupp所说,我回答了一个不同的,更简单的问题。正确的理解产生

import Data.List
import qualified Data.Set as S

groupN :: Ord a => Int -> [a] -> [[a]]
groupN n (h:t) = reverse . fmap reverse . fst $
                 foldl' add ([[h]], S.singleton h) t
  where insHead (l:t) i = (i:l):t
        add (l, s) i
          | i `S.member` s = (insHead l i, s)
          | S.size s == n  = ([i]:l, S.singleton i)
          | True           = (insHead l i, S.insert i s)

这是(我认为)正确且相当简洁,并且相对于其输入以线性时间运行(对于m个唯一值的组和长度为n的输入列表,为O(n log m);理论最大值为O( n)使用具有恒定时间插入和查找的数据结构,我认为dbaupp在O(mn)中运行。但是,我确实通过使用集合来加强条件Eq aOrd a,并牺牲懒惰。


错误的代码:

import Data.List

groupN :: Eq a => Int -> [a] -> [[a]]
groupN n = concatN n . group
  where concatN n l = case splitAt n l of
          (l, [])  -> return $ concat l
          (l1, l2) -> (concat l1):(concatN n l2)

您可以使用genericSplitAt放宽IntIntegral