List 反转列表的前k个元素
我想有效地反转列表的前k个元素 这就是我的想法:List 反转列表的前k个元素,list,haskell,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] 这相当不错,但是(++)让我
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]
这相当不错,但是
(++)
让我很困扰。或者我应该考虑使用另一种数据结构吗?我想用简短的列表来做这件事无数次。好吧,通常我只需要写splitAt 3>>第一次反转>>>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个元素。实际性能将在很大程度上取决于编译器能够融合的内容。让我们考虑一下
反转的常见结构:
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
中检查过,所以一切正常。查看生成的core(使用ghc-O2-ddump siml-dsuppress all-dno suppress type signatures
)表明,这些对被编译成未折叠的对,而Int
s被解除绑定,因此一切可能都非常有效
分析表明,该实现的速度大约是差异列表1的1.3倍,分配的内存大约是差异列表1的65%。我觉得代码还行。我想真正的问题是:你为什么要这样做?我想解决这个问题。目前的解决方案既慢又难看。对。因此,这是一个基准测试,看看你能多快地交换东西,而不是产生一个实际有用的结果……如果你是为了一个基准测试,而不是其他使用数组而不是链表的语言,我会使用数组而不是链表。否则,这种比较是非常不公平的,IMHO。目前的解决方案确实使用数组。我不知道为什么你认为它是“缓慢而丑陋的”,但是…或者为什么你认为链接列表会胜过数组。如果我正确地读取了STG(大IF),那么你就为每个反向元素建立了一个闭包,至少在GHC 7.83.