反转列表

时间:2016-02-22 17:02:24

标签: list haskell

我想有效地反转列表的前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]

这是相当不错的,但(++)困扰我。或者我应该考虑使用其他数据结构?我想用短名单做这么多次。

2 个答案:

答案 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 元素。实际性能将取决于编译器能够融合的内容。