web-dev-qa-db-ja.com

リストの最後のn個の要素を取得するにはどうすればよいですか

リストnの最後のxs要素を取得するには、reverse (take n (reverse xs))を使用できますが、これはあまり良いコードではありません(何かを返す前に完全なリストをメモリに保持します) 、および結果は元のリストと共有されません)。

このlastR関数をHaskellに実装するにはどうすればよいですか?

20

これには、リストの長さを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

Satvikが指摘したように、再帰演算子を使用する方が明示的な再帰を使用する方がよい場合が多いので、これはより短い代替案です。

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

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

また、takeLeftoverは次のとおりであるというWillNessのコメントにも注意してください。

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
19
Davorak

私が言えることから、あなたは次のようなものを使うことができます

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

ただし、組み込みリストの実装では、O(length of list - n)よりも優れたパフォーマンスを発揮することはできません。

リストを効率的に実行するためのものではないものに使用しようとしているようです。 _Data.Sequence_ またはリストの最後で効率的に操作を実行できるようにする、リストの他の実装を使用します。


編集:

Davorakの実装は、組み込みリストから取得できる最も効率的な実装のようです。ただし、他の関数とうまく融合するかどうかなど、単一の関数の実行時間以外にも複雑な点があることを忘れないでください。

Danielのソリューションは組み込み関数を使用し、Davorakのそれと同じ複雑さを持ち、他の関数と融合する可能性が高いと思います。

9
Satvik

それがひどく速いかどうかはわかりませんが、それは簡単です:

lastR n xs = snd $ dropWhile (not . null . fst) $ Zip (tails $ drop n xs) (tails xs)
5
Daniel Fischer

何をするにしても、リスト全体を繰り返す必要があることに注意してください。そうは言っても、最初にリストの長さを計算し、適切な数の要素を削除することで、reverse (take n (reverse xs))よりも少しうまくいくことができます。

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

以下は、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には何の違いもありませんが、他のアプリケーションでは、リストの融合に勝つ可能性があります。

1
dfeuer

シンプルなソリューションはそれほど悪くはありません。とにかくアルゴリズムは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 が質問で指摘したように、コメントにはまだメモリの問題があります。他のソリューションよりもそれほど遅くはありませんが、そのようなソリューションはほぼ2倍のメモリを必要とします。これはベンチマークで確認できます。

0
Nolan