Как взять последние 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