Как взять последние n элементов списка

Чтобы получить последние n элементы списка xs, я могу использовать reverse (take n (reverse xs)), но это не очень хороший код (он сохраняет полный список в памяти перед возвратом чего-либо, а результат не используется с исходным списком).

Как реализовать эту функцию lastR в Haskell?

Ответы

Ответ 1

Это должно иметь свойство только повторять длину списка один раз. 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

Вот альтернатива короче и, возможно, лучше, поскольку, как указал Сатвик, часто лучше использовать рекурсионные операторы, а затем явную рекурсию.

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

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

Также обратите внимание на комментарий Уэса ниже, что 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

Ответ 2

Из того, что я могу сказать, вы можете использовать что-то вроде

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

Но с любой реализацией из встроенного списка вы не можете работать лучше, чем O(length of list - n).

Похоже, вы пытаетесь использовать список для чего-то, что не предназначалось для эффективной работы. Используйте Data.Sequence или некоторую другую реализацию списка, которая позволяет эффективно выполнять операции в конце списка.


Редактировать:

Реализация Davorak выглядит наиболее эффективной реализацией, которую вы можете получить из встроенного списка. Но помните, что есть и другие сложности, а не только время работы одной функции, например, хорошо ли она сочетается с другими функциями и т.д.

Решение Daniel использует встроенные функции и имеет ту же сложность, что и Davorak, и я думаю, что имеет больше шансов слиться с другими функциями.

Ответ 3

Не уверен, что это ужасно быстро, но это легко:

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

Ответ 4

Имейте в виду, что, что бы вы ни делали, вам нужно будет перебирать весь список. Тем не менее, вы можете сделать немного лучше, чем reverse (take n (reverse xs)), сначала вычислив длину списка и отбросив соответствующее количество элементов:

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

Ответ 5

Простое решение не так уж плохо. Алгоритм в любом случае O (n).

takeLastN n = reverse . take n . reverse

Сравнение времени:

> length $ lastN 3000000 (replicate 10000000 "H") -- Davorak solution #1
3000000
(0.88 secs, 560,065,232 bytes)
> length $ lastN' 3000000 (replicate 10000000 "H") -- Davorak solution #2
3000000
(1.82 secs, 840,065,096 bytes)
> length $ lastN'' 3000000 (replicate 10000000 "H") -- Chris Taylor 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)

Как указал Иоахим Брейтнер в вопросе и в комментарии, до сих пор существует проблема с памятью. Хотя не намного медленнее, чем другие, такое решение требует почти вдвое больше памяти. Вы можете увидеть это в тестах.

Ответ 6

Вот упрощение Даворака первым решением:

-- 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, но в других приложениях это может выиграть слияние со списком.

Ответ 7

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