21

要获取n列表的最后一个元素xs,我可以使用reverse (take n (reverse xs)),但这不是很好的代码(它在返回任何内容之前将完整的列表保存在内存中,并且结果不与原始列表共享)。

如何lastR在 Haskell 中实现这个功能?

4

7 回答 7

22

这应该具有仅迭代列表长度一次的属性。N 代表drop n和 n - 1 代表 zipLeftover。

zipLeftover :: [a] -> [a] -> [a]
zipLeftover []     []     = []
zipLeftover xs     []     = xs
zipLeftover []     ys     = ys
zipLeftover (x:xs) (y:ys) = zipLeftover xs ys

lastN :: Int -> [a] -> [a]
lastN n xs = zipLeftover (drop n xs) xs

这是一个更短,也许更好的替代方案,因为正如 Satvik 指出的那样,使用递归运算符通常比使用显式递归更好。

import Data.Foldable

takeLeftover :: [a] -> t -> [a]
takeLeftover [] _ = []
takeLeftover (x:xss) _ = xss

lastN' :: Int -> [a] -> [a]
lastN' n xs = foldl' takeLeftover xs (drop n xs)

另请注意 Will Ness 在下面的评论takeLeftover只是:

takeLeftover == const . drop 1

这使事情变得相当整洁:

lastN' :: Int -> [a] -> [a]
lastN' n xs = foldl' (const . drop 1) xs (drop n xs)
-- or
-- lastN' n xs = foldl' (const . drop 1) <*> drop n
于 2013-06-22T16:51:55.350 回答
12

据我所知,您可以使用类似的东西

lastN :: Int -> [a] -> [a]
lastN n xs = drop (length xs - n) xs

但是对于内置列表上的任何实现,您都无法表现得比O(length of list - n).

看起来您正在尝试将 list 用于无法有效执行的操作。使用Data.Sequence列表或其他一些实现,它允许在列表末尾有效地执行操作。


编辑:

Davorak 的实现看起来是您可以从内置列表中获得的最有效的实现。但请记住,除了单个函数的运行时间之外,还有一些复杂的问题,比如它是否与其他函数融合得很好等。

Daniel 的解决方案使用内置函数,具有与 Davorak 相同的复杂性,我认为与其他函数融合的机会更大。

于 2013-06-22T16:46:36.273 回答
5

不确定它是否非常快,但很容易:

lastR n xs = snd $ dropWhile (not . null . fst) $ zip (tails $ drop n xs) (tails xs)
于 2013-06-22T16:52:09.060 回答
1

请注意,无论您做什么,都需要遍历整个列表。reverse (take n (reverse xs))也就是说,与先计算列表的长度并删除适当数量的元素相比,您可以做得更好:

lastN :: Int -> [a] -> [a]
lastN n xs = let m = length xs in drop (m-n) xs
于 2013-06-22T16:46:55.917 回答
1

这是 Davorak 的第一个解决方案的简化:

-- dropLength bs = drop (length bs)
dropLength :: [b] -> [a] -> [a]
dropLength [] as = as
dropLength _ [] = []
dropLength (_ : bs) (_ : as) = dropLength bs as

lastR :: Int -> [a] -> [a]
lastR n as = dropLength (drop n as) as

n <= length as, length (drop n as) = length as - n, 所以dropLength (drop n as) as = drop (length (drop n as)) as = drop (length as - n) as, 是 的最后一个n元素as。当n > length as, dropLength (drop n as) as = dropLength [] as = as, 这是唯一合理的答案。

如果你想使用折叠,你可以写

dropLength :: [b] -> [a] -> [a]
dropLength = foldr go id
  where
     go _b _r [] = []
     go _b r (_a : as) = r as

这对 没有任何lastR影响,但在其他应用程序中它可以为您赢得一些列表融合。

于 2019-06-24T16:22:03.283 回答
0

简单的解决方案还不错。无论如何,算法都是 O(n)。

takeLastN n = reverse . take n . reverse

时间比较:

> length $ lastN 3000000 (replicate 10000000 "H") -- Davorak's solution #1
3000000
(0.88 secs, 560,065,232 bytes)
> length $ lastN' 3000000 (replicate 10000000 "H") -- Davorak's solution #2
3000000
(1.82 secs, 840,065,096 bytes)
> length $ lastN'' 3000000 (replicate 10000000 "H") -- Chris Taylor's solution
3000000
(0.50 secs, 560,067,680 bytes)
> length $ takeLastN 3000000 (replicate 10000000 "H") -- Simple solution
3000000
(0.81 secs, 1,040,064,928 bytes)

正如Joachim Breitner在问题和评论中指出的那样,仍然存在内存问题。比其他解决方案慢不了多少,这样的解决方案需要几乎两倍的内存。您可以在基准测试中看到这一点。

于 2019-06-24T09:53:18.200 回答
-1
takeLast :: Int -> [a] -> [a]
takeLast n xs 
 | n < 1 = []
 | otherwise = let s = splitAt n xs in bla (fst s) (snd s)
 where 
  bla xs [] = xs
  bla (x:xs) (y:ys) = bla (xs ++ [y]) ys
于 2016-12-27T00:12:49.377 回答