簡體   English   中英

如何獲取列表的最后 n 個元素

[英]How do I take the last n elements of a list

要獲取列表xs的最后n元素,我可以使用reverse (take n (reverse xs)) ,但這不是很好的代碼(它在返回任何內容之前將完整列表保留在內存中,並且結果不與原始列表)。

如何在 Haskell 中實現這個lastR函數?

這應該具有僅迭代列表長度一次的屬性。 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

據我所知,您可以使用類似的東西

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

但是對於內置列表的任何實現,您的表現都不會比O(length of list - n)

看起來您正在嘗試將 list 用於它並不意味着高效執行的事情。 使用Data.Sequence或列表的其他一些實現,它允許在列表的末尾有效地執行操作。


編輯:

Davorak 的實現看起來是您可以從內置列表中獲得的最有效的實現。 但請記住,除了單個函數的運行時間之外,還有一些復雜的問題,比如它是否與其他函數融合得很好等。

Daniel 的解決方案使用內置函數並且具有與 Davorak 相同的復雜性,我認為有更好的機會與其他函數融合。

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

lastR n xs = snd $ dropWhile (not . null . fst) $ zip (tails $ drop n xs) (tails xs)

請注意,無論您做什么,都需要遍歷整個列表。 也就是說,您可以通過首先計算列表的長度並刪除適當數量的元素來比reverse (take n (reverse xs))做得更好:

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

這是 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 aslength (drop n as) = length as - n ,所以dropLength (drop n as) as = drop (length (drop n as)) as = drop (length as - n) as ,即as的最后n元素。 n > length asdropLength (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沒有任何lastR ,但在其他應用程序中,它可以為您贏得一些列表融合。

簡單的解決方案並沒有那么糟糕。 無論如何,算法是 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在問題和評論中指出的那樣,仍然存在內存問題。 盡管並不比其他解決方案慢多少,但此類解決方案需要幾乎兩倍的內存。 您可以在基准測試中看到這一點。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM