我想以某种方式对列表进行分组,每个组都尽可能大,并且最多包含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
。
我还没有想出一个很好的方法来实现它。你呢?解决方案应该尽可能通用,因为我需要对组进行更多的条件。
答案 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
的两个递归情况具有几乎相同的结构,除了递归调用中值count
和seen
之间的差异之外,可以通过以上方式加以说明因此,分解可能很容易使功能不那么干净。)
groupN
可以通过重复应用splitNDistinct
来实现。
考虑到这一点,可以定义mapFst f (a,b) = (f a, b)
并在let
的递归调用中分别用go
和mapFst (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 a
到Ord 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
放宽Int
至Integral
。