列表性能的长度为n的子序列

时间:2014-01-21 17:46:48

标签: performance haskell

我实施了此答案https://stackoverflow.com/a/9920425/1261166的一个版本(我不知道回答的人的目的是什么)

sublistofsize 0 _        = [[]]
sublistofsize _ []       = []
sublistofsize n (x : xs) = sublistsThatStartWithX ++ sublistsThatDontStartWithX
  where sublistsThatStartWithX = map (x:) $ sublistofsize (n-1) xs
        sublistsThatDontStartWithX = sublistofsize n xs

我不确定的是sublistsThatStartWithX = map (x:) $ sublistofsize (n-1) xs

我认为map(x :)在性能方面存在问题,但不确定如何解决它。我已经对print $ length $ sublistofsize 5 $ primesToTakeFrom 50

进行了分析
COST CENTRE                                  MODULE                                        no.     entries  %time %alloc   %time %alloc
sublistofsize                             Main                                          112     4739871   46.9   39.9    96.9  100.0
 sublistofsize.sublistsThatDontStartWithX Main                                          124     2369935    2.2    0.0     2.2    0.0
 sublistofsize.sublistsThatStartWithX     Main                                          116     2369935   47.8   60.1    47.8   60.1

我是否以良好的方式实施了它? 有没有更快的方法呢?

4 个答案:

答案 0 :(得分:13)

  

我认为map(x :)给出了一个明智的性能问题

没有。 map被有效编码并以线性时间运行,这里没有问题。

但是,您的递归可能是个问题。您同时致电sublistofsize (n-1) xssublistofsize n xs,其中 - 在给定开始列表sublistofsize m (_:_:ys)的情况下 - 会对sublistofsize (m-1) ys项进行两次评估,因为它们之间没有共享递归步骤。

所以我应用动态编程来获取

subsequencesOfSize :: Int -> [a] -> [[a]]
subsequencesOfSize n xs = let l = length xs
                          in if n>l then [] else subsequencesBySize xs !! (l-n)
 where
   subsequencesBySize [] = [[[]]]
   subsequencesBySize (x:xs) = let next = subsequencesBySize xs
                             in zipWith (++) ([]:next) (map (map (x:)) next ++ [[]])

不是附加空列表是最美丽的解决方案,但您可以看到我如何将zipWith与移位列表一起使用,以便next的结果使用两次 - 一次直接在长度为n的子序列列表和长度为n + 1的子序列列表中的一次。

使用:set +s在GHCI中对其进行测试,您可以看到这比天真的解决方案快得多:

*Main> length $ subsequencesOfSize 7 [1..25]
480700
(0.25 secs, 74132648 bytes)
(0.28 secs, 73524928 bytes)
(0.30 secs, 73529004 bytes)
*Main> length $ sublistofsize 7 [1..25] -- @Vixen (question)
480700
(3.03 secs, 470779436 bytes)
(3.35 secs, 470602932 bytes)
(3.14 secs, 470747656 bytes)
*Main> length $ sublistofsize' 7 [1..25] -- @Ganesh
480700
(2.00 secs, 193610388 bytes)
(2.00 secs, 193681472 bytes)
*Main> length $ subseq 7 [1..25] -- @user5402
480700
(3.07 secs, 485941092 bytes)
(3.07 secs, 486279608 bytes)

答案 1 :(得分:2)

您的实现是该问题的自然“Haskell-ish”。

如果您最终使用整个结果,那么在给定输出数据结构([[a]])的情况下,对于此问题,将不会有任何渐近更快的因为它在输出长度上按时间线性运行。

使用map (x:)是一种非常自然的方式,可以在每个列表的开头添加一个元素,并且由于我们正在使用列表,因此不太可能有任何明显更快的选项。

原则上重复使用(++)是低效的,因为它会导致每次调用时都会遍历左侧参数,但在这种情况下总成本应该只是一个额外的常数因子。

您可以使用累积参数otherResults来改善结果,但要进行此更改,您还需要按相反的顺序向下传递prefix并重新反向最后,这可能会耗尽储蓄:

sublistofsize' 0 _        prefix otherResults = reverse prefix : otherResults
sublistofsize' _ []       prefix otherResults = otherResults
sublistofsize' n (x : xs) prefix otherResults =
   sublistofsize' (n-1) xs (x:prefix) (sublistofsize' n xs prefix otherResults)

sublistofsize n xs = sublistofsize' n xs [] []

答案 2 :(得分:1)

应该有用的优化是跟踪列表中是否有足够的元素来形成子序列的其余部分。这可以通过跟踪n-1 - xs之前的元素并在你递归时推进这两个元素来非常有效地完成。

实施:

  nthtail 0 xs = xs
  nthtail _ [] = []
  nthtail n (x:xs) = nthtail (n-1) xs

  subseq 0 _ = [[]]
  subseq n xs =
    if null t
      then []
      else go n xs t
    where
      t = nthtail (n-1) xs  -- n should always be >= 1 here
      go 0 _ _  =  [[]]
      go _ _ [] = []
      go n xs@(x:xt) t = withx ++ withoutx
        where withx = map (x:) $ go (n-1) xt t
              withoutx = go n xt (tail t)

答案 3 :(得分:0)

这是一个已有6年历史的话题,但是我认为我有一个值得在这里分享的代码。

@Bergi接受的答案只是超级好,但从两个方面来看,我仍然认为这项工作可以做得更好;

  1. 尽管未在任何规范中提及,但它以相反的字典顺序返回组合。通常情况下,您可能希望按字典顺序排列它们。
  2. 在使用C(n,n / 2)进行测试时,它们的性能相似,但是当像C(100,5)进行测试时,以下代码则更快,内存效率更高。

combinationsOf :: Int -> [a] -> [[a]]
combinationsOf 1 as        = map pure as
combinationsOf k as@(x:xs) = run (l-1) (k-1) as $ combinationsOf (k-1) xs
                             where
                             l = length as

                             run :: Int -> Int -> [a] -> [[a]] -> [[a]]
                             run n k ys cs | n == k    = map (ys ++) cs
                                           | otherwise = map (q:) cs ++ run (n-1) k qs (drop dc cs)
                                           where
                                           (q:qs) = take (n-k+1) ys
                                           dc     = product [(n-k+1)..(n-1)] `div` product [1..(k-1)]

让我们将它们与接受的答案下的测试用例进行比较。

*Main> length $ subsequencesOfSize 7 [1..25]
480700
(0.27 secs, 145,572,672 bytes)

*Main> length $ combinationsOf 7 [1..25]
480700
(0.14 secs, 95,055,360 bytes)

让我们针对诸如C(100,5)之类的更难的东西进行测试

*Main> length $ subsequencesOfSize 5 [1..100]
75287520
(52.01 secs, 77,942,823,360 bytes)

*Main> length $ combinationsOf 5 [1..100]
75287520
(17.61 secs, 11,406,834,912 bytes)