我有一组视图对,它们代表内容标签列表及其宽度我希望按行分组(如果下一个内容标签不符合行,则将其放入另一行)。所以我们有:viewList = [(View1, 45), (View2, 223.5), (View3, 14) (View4, 42)]
。
我想编写一个函数groupViews :: [a] -> [[a]]
来将此列表分组到一个子列表列表中,其中每个子列表仅包含宽度小于最大指定宽度的视图(假设为250
)。
因此,对于已排序的viewList
,此函数将返回:[[(View3, 14), (View4, 42), (View1, 45)],[(View2, 223.5)]]
看起来类似于groupBy
。但是,groupBy
不维护累加器。我尝试使用scanl
+ takeWhile(<250)
组合,但在这种情况下,我只能收到第一个有效的子列表。也许以某种方式使用iterate
+ scanl
+ takeWhile
?但这看起来非常麻烦,根本不起作用。任何帮助将不胜感激。
答案 0 :(得分:4)
我会从这样的递归定义开始:
groupViews :: Double -> (a -> Double) -> [a] -> [[a]]
groupViews maxWidth width = go (0, [[]])
where
go (current, acc : accs) (view : views)
| current + width view <= maxWidth
= go (current + width view, (view : acc) : accs) views
| otherwise = go (width view, [view] : acc : accs) views
go (_, accs) []
= reverse $ map reverse accs
像[{1}}一样调用。我注意到的第一件事是它可以表示为左折:
groupViews 250 snd (sortOn snd viewList)
我认为这很好,但如果你愿意的话可以进一步考虑,在一次扫描中累积宽度以模数最大宽度,另一次传递将元素分组为升序。例如,这是一个适用于整数宽度的版本:
groupViews' maxWidth width
= reverse . map reverse . snd . foldl' go (0, [[]])
where
go (current, acc : accs) view
| current + width view <= maxWidth
= (current + width view, (view : acc) : accs)
| otherwise
= (width view, [view] : acc : accs)
当然,您可以在这些定义中包含排序,而不是从外部传递排序列表。
答案 1 :(得分:3)
我不知道通过组合标准库中的函数来实现这一目的的聪明方法,但我认为你可以做得比从头开始实现它更好。
这个问题适合我以前见过的一类问题:“以某种方式从这个列表中批量处理项目,并根据某些组合规则将其项目合并为批次,并根据某个规则来确定批次何时过大” 。多年前,当我写Clojure时,我built a function抽象出了这种批量组合的想法,只是要求你指定批处理的规则,并且能够在令人惊讶的数量的地方使用它。
以下是我认为可能在Haskell中重新构想的方式:
glue :: Monoid a => (a -> Bool) -> [a] -> [a]
glue tooBig = go mempty
where go current [] = [current]
go current (x:xs) | tooBig x' = current : go x xs
| otherwise = go x' xs
where x' = current `mappend` x
如果你已经有这样的glue
函数,你可以用适当的Monoid
实例(对象列表及其累积和)构建一个简单的数据类型,然后让{{1}做重物:
glue
一方面,对于一个简单的函数来说,这看起来相当多,但另一方面,有一个描述你正在进行的累积求和的类型是相当不错的,而且Monoid实例无论如何都很好。 ..在定义类型和Monoid实例之后,在调用import Data.Monoid (Sum(..))
data ViewGroup contents size = ViewGroup {totalSize :: size,
elements :: [(contents, size)]}
instance Monoid b => Monoid (ViewGroup a b) where
mempty = ViewGroup mempty []
mappend (ViewGroup lSize lElts) (ViewGroup rSize rElts) =
ViewGroup (lSize `mappend` rSize)
(lElts ++ rElts)
viewGroups = let views = [("a", 14), ("b", 42), ("c", 45), ("d", 223.5)]
in glue ((> 250) . totalSize) [ViewGroup (Sum width) [(x, Sum width)]
| (x, width) <- views]
main = print (viewGroups :: [ViewGroup String (Sum Double)])
[ViewGroup {totalSize = Sum {getSum = 101.0},
elements = [("a",Sum {getSum = 14.0}),
("b",Sum {getSum = 42.0}),
("c",Sum {getSum = 45.0})]},
ViewGroup {totalSize = Sum {getSum = 223.5},
elements = [("d",Sum {getSum = 223.5})]}]
本身时几乎没有工作要做。
好吧,我不知道,也许这仍然是太多的工作,特别是如果你不相信你可以重用那种类型。但我认为认识到这是一个更普遍的问题的特定情况是有用的,并尝试解决更普遍的问题。
答案 2 :(得分:2)
鉴于groupViews'' maxWidth width views
= map fst
$ groupBy ((<) `on` snd)
$ zip views
$ drop 1
$ scanl (\ current view -> (current + width view) `mod` maxWidth) 0 views
和groupBy
本身是由手动递归函数定义的,我们修改的函数将使用相同的机制。
让我们首先定义一个通用函数span
,它取累加器的初始值,然后是一个函数,它接受列表中的元素,当前累加器状态并可能产生新的累积值(Nothing表示不接受该元素):
groupAcc
对于我们的具体问题,我们定义:
{-# LANGUAGE LambdaCase #-}
import Data.List (sortOn)
import Control.Arrow (first, second)
spanAcc :: z -> (a -> z -> Maybe z) -> [a] -> ((z, [a]), [a])
spanAcc z0 p = \case
xs@[] -> ((z0, xs), xs)
xs@(x:xs') -> case p x z0 of
Nothing -> ((z0, []), xs)
Just z1 -> first (\(z2, xt) -> (if null xt then z1 else z2, x : xt)) $
spanAcc z1 p xs'
groupAcc :: z -> (a -> z -> Maybe z) -> [a] -> [(z, [a])]
groupAcc z p = \case
[] -> [] ;
xs -> uncurry (:) $ second (groupAcc z p) $ spanAcc z p xs
最终给了我们:
threshold :: (Num a, Ord a) => a -> a -> a -> Maybe a
threshold max a z0 = let z1 = a + z0 in if z1 < max then Just z1 else Nothing
groupViews :: (Ord z, Num z) => [(lab, z)] -> [[(lab, z)]]
groupViews = fmap snd . groupAcc 0 (threshold 250 . snd)
ghci给了我们:
groupFinal :: (Num a, Ord a) => [(lab, a)] -> [[(lab, a)]]
groupFinal = groupViews . sortOn snd
如果我们愿意,我们可以通过假设> groupFinal [("a", 45), ("b", 223.5), ("c", 14), ("d", 42)]
[[("c",14.0),("d",42.0),("a",45.0)],[("b",223.5)]]
是groupAcc
来z
来简化Monoid
,因此可以使用mempty
,这样:
groupAcc2 :: Monoid z => (a -> z -> Maybe z) -> [a] -> [(z, [a])]
groupAcc2 p = \case
[] -> [] ;
xs -> let z = mempty in
uncurry (:) $ second (groupAcc z p) $ spanAcc z p xs