我想有效地反转列表的前k个元素。
这就是我想出的:
reverseFirst :: Int -> [a] -> [a] -> [a]
reverseFirst 0 xs rev = rev ++ xs
reverseFirst k (x:xs) rev = reverseFirst (k-1) xs (x:rev)
reversed = reverseFirst 3 [1..5] mempty -- Result: [3,2,1,4,5]
这是相当不错的,但(++)
困扰我。或者我应该考虑使用其他数据结构?我想用短名单做这么多次。
答案 0 :(得分:5)
让我们考虑一下reverse
的通常结构:
reverse = rev [] where
rev acc [] = acc
rev acc (x : xs) = rev (x : acc) xs
它从空列表开始,并从参数列表前面的元素开始,直到它完成。我们想要做类似的事情,除了我们想要将元素添加到列表中我们不会反转的部分的前面。当我们没有& 那个未反转的部分时,我们怎么能这样做?
我能想到的最简单的方法是避免遍历列表的前面两次是使用懒惰:
reverseFirst :: Int -> [a] -> [a]
reverseFirst k xs = dis where
(dis, dat) = rf dat k xs
rf acc 0 ys = (acc, ys)
rf acc n [] = (acc, [])
rf acc n (y : ys) = rf (y : acc) (n - 1) ys
dat
表示单独留下的列表部分。我们在执行逆转的同一辅助函数rf
中计算它,但我们也在初始调用中将它传递给rf
。它从未在rf
中进行过实际检查,因此一切正常。查看生成的核心(使用ghc -O2 -ddump-simpl -dsuppress-all -dno-suppress-type-signatures
)表明这些对被编译成未提升的对,而Int
是未装箱的,所以一切都应该非常有效。
分析表明,此实现的速度大约是差异列表的1.3倍,并且分配的内存大约为65%。
答案 1 :(得分:2)
好吧,通常我只是写splitAt 3 >>> first reverse >>> uncurry(++)
来实现目标。
如果您对性能感到焦虑,可以考虑差异列表:
reverseFirstN :: Int -> [a] -> [a]
reverseFirstN = go id
where go rev 0 xs = rev xs
go rev k (x:xs) = go ((x:).rev) (k-1) xs
但坦率地说,我不希望这会快得多:你需要以任何一种方式遍历第一个 n 元素。实际性能将取决于编译器能够融合的内容。