在许多系统中,head.reverse
需要与列表大小成比例的空间,而last
需要恒定的空间。
是否有系统可以执行此类转换?同样适用于reverse.take n.reverse
?
编辑:我想扩展一下我的问题:我不是经过一次具体的转变 - 我宁愿在任何优化之后。
答案 0 :(得分:5)
您可以通过将列表视为特别迟钝的自然数来转换reverse . take n . reverse
:空列表为零,并且conses为succ。对于编码为列表的惰性自然,减法为drop
:
type LazyNat a = [a]
lengthLazy :: [a] -> LazyNat a
lengthLazy = id
dropLazy :: LazyNat a -> [b] -> [b]
dropLazy [] xs = xs
dropLazy (_:n) (_:xs) = dropLazy n xs
dropLazy _ _ = []
-- like Prelude.subtract, this is flip (-)
subtractLazy :: Int -> LazyNat a -> LazyNat a
subtractLazy = drop
现在我们可以轻松实现“采取最后n
”功能:
takeLast n xs = dropLazy (subtractLazy n (lengthLazy xs)) xs
...你会很高兴地知道在任何特定时间只需要n
个记忆。特别是,takeLast 1
(或任何文字takeLast N
的{{1}})可以在常量内存中运行。您可以通过比较运行N
时发生的情况与在ghci中运行takeLast 5 [1..]
时发生的情况进行比较来验证这一点。
当然,我尝试使用上面非常具有暗示性的名称,但在实际实现中,您可能会在上面列出所有废话:
(reverse . take 5 . reverse) [1..]
答案 1 :(得分:1)
答案 2 :(得分:1)
如果我们比较drop和last
>>> last [1..10^5]
100000
(0.01 secs, 10496736 bytes)
>>> last [1..10^6]
1000000
(0.05 secs, 81968856 bytes)
>>> last [1..10^7]
10000000
(0.32 secs, 802137928 bytes)
>>> drop (10^5-1) [1..10^5]
[100000]
(0.01 secs, 10483312 bytes)
>>> drop (10^6-1) [1..10^6]
[1000000]
(0.05 secs, 82525384 bytes)
>>> drop (10^7-1) [1..10^7]
[10000000]
(0.32 secs, 802142096 bytes)
我们在空间和时间上获得类似的表现,我必须承认我有点欺骗,因为在这里我们不需要计算列表的长度。无论如何,我相信它不应该是太空问题。然后你的反向。拿n。可以使用drop和length表示反向。
作为旁注,我已经测试了其他解决方法,结果很糟糕。
takeLastN = foldl' (.) id . flip replicate tail
lastN = foldl' (.) last . flip replicate init