要获取列表n
的最后xs
个元素,我可以使用reverse (take n (reverse xs))
,但这不是很好的代码(它会在返回任何内容之前将完整列表保留在内存中,并且结果不与原始列表共享。)
如何在Haskell中实现此lastR
函数?
答案 0 :(得分:19)
这应该具有仅迭代列表长度一次的属性。 N为drop n
,n为 - 为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指出使用递归运算符然后显式递归通常会更好。
takeLeftover :: [a] -> t -> [a]
takeLeftover [] _ = []
takeLeftover (x:xss) _ = xss
lastN' :: Int -> [a] -> [a]
lastN' n xs = foldl' takeLeftover xs (drop n xs)
另请注意,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
答案 1 :(得分:9)
据我所知,你可以使用像
这样的东西lastN :: Int -> [a] -> [a]
lastN n xs = drop (length xs - n) xs
但是对于内置列表中的任何实现,您的效果都不能超过O(length of list - n)
。
看起来你正在尝试使用list来表示它本身无法有效执行的操作。
使用Data.Sequence
或其他一些列表实现,可以有效地在列表末尾执行操作。
编辑:
Davorak的实施看起来是您可以从内置列表中获得的最有效的实现。但请记住,除了单个函数的运行时间之外,还有复杂性,例如它是否与其他函数融合等。
Daniel的解决方案使用内置函数,并且具有与Davorak相同的复杂性,我认为有更好的机会与其他函数融合。
答案 2 :(得分:4)
不确定它是否非常快,但很容易:
lastR n xs = snd $ dropWhile (not . null . fst) $ zip (tails $ drop n xs) (tails xs)
答案 3 :(得分:1)
请注意,无论您做什么,您都需要遍历整个列表。也就是说,通过先计算列表的长度,然后删除适当数量的元素,你可以比reverse (take n (reverse xs))
做得更好:
lastN :: Int -> [a] -> [a]
lastN n xs = let m = length xs in drop (m-n) xs
答案 4 :(得分: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
来说,这没有什么区别,但是在其他应用程序中,它可以赢得一些列表融合。
答案 5 :(得分: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在问题和评论中指出的那样,仍然存在内存问题。尽管这种解决方案的速度并不比其他解决方案慢很多,但它几乎需要两倍的内存。您可以在基准测试中看到这一点。
答案 6 :(得分:-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