我已经概括了现有的Data.List.partition
实现
partition :: (a -> Bool) -> [a] -> ([a],[a])
partition p xs = foldr (select p) ([],[]) xs
where
-- select :: (a -> Bool) -> a -> ([a], [a]) -> ([a], [a])
select p x ~(ts,fs) | p x = (x:ts,fs)
| otherwise = (ts, x:fs)
到“三分区”功能
ordPartition :: (a -> Ordering) -> [a] -> ([a],[a],[a])
ordPartition cmp xs = foldr select ([],[],[]) xs
where
-- select :: a -> ([a], [a], [a]) -> ([a], [a], [a])
select x ~(lts,eqs,gts) = case cmp x of
LT -> (x:lts,eqs,gts)
EQ -> (lts,x:eqs,gts)
GT -> (lts,eqs,x:gts)
但是现在我在使用ghc -O1
进行编译时遇到了令人困惑的行为,'foo'和'bar'函数在恒定空间中工作,但doo
函数导致空间泄漏
foo xs = xs1
where
(xs1,_,_) = ordPartition (flip compare 0) xs
bar xs = xs2
where
(_,xs2,_) = ordPartition (flip compare 0) xs
-- pass-thru "least" non-empty partition
doo xs | null xs1 = if null xs2 then xs3 else xs2
| otherwise = xs1
where
(xs1,xs2,xs3) = ordPartition (flip compare 0) xs
main :: IO ()
main = do
print $ foo [0..100000000::Integer] -- results in []
print $ bar [0..100000000::Integer] -- results in [0]
print $ doo [0..100000000::Integer] -- results in [0] with space-leak
所以现在我的问题是,
doo
中空间泄漏的原因是什么,这似乎让我感到惊讶,因为foo
和bar
没有表现出这样的空间泄漏?以及
有没有办法以这样的方式实现ordPartition
,当在doo
等函数的上下文中使用时,它会以恒定的空间复杂度执行?
答案 0 :(得分:5)
这不是空间泄漏。要确定组件列表是否为空,必须遍历整个输入列表,并构建其他组件列表(如果是thunk)。在doo
情况下,xs1
为空,因此在决定输出内容之前必须构建整个事物。
这是所有分区算法的基本属性,如果其中一个结果为空,并且您检查其空白作为条件,则在遍历整个列表之前无法完成该检查。