开发者

Longest non decrease subseq in Haskell is slow. How to improve?

开发者 https://www.devze.com 2023-03-07 15:51 出处:网络
longest\'inc\'subseq seq = maximum dp where dp = 1 : [val n | n <- [1..length seq - 1]] val n = (1 +) . filter\'and\'get\'max ((<= top) . (seq!!)) $ [0..pred n]
longest'inc'subseq seq = maximum dp
    where dp = 1 : [val n | n <- [1..length seq - 1]]
          val n = (1 +) . filter'and'get'max ((<= top) . (seq!!)) $ [0..pred n]
            where top = seq!!n
          -----
          filter'and'get'max f []     = 0
          filter'and'get'max f [x]    = if f x then dp!!x else 0
          filter'and'get'max f (x:xs) = if f x then ( if vx > vxs then vx else vxs ) else vxs
            where vx  = dp!!x
                  vxs = filter'and'get'max f xs

that take about 1-2s with lenght of seq = 1000 while in python is come out imtermedialy

in python

def longest(s):
    dp = [0]*len(s)
    dp[0] = 1
    for i in range(1,len(s)):
        need = 0
        for j in range (0, i):
            if s[j] <= s[i] and dp[j] > need:
                need = dp[j]
        dp[i] = need + 1
    return max(dp)

and when lengt开发者_开发技巧h of seq is 10000, the haskell program run sooo long while python return the answer after 10-15s

Can we improve haskell speed?


Your core problem is that you're using the wrong data structure in Haskell for this algorithm. You've translated an algorithm that depends on O(1) lookups on a sequence (as in your Python code), into one that does O(n) lookups on a list in Haskell.

Use like-for-like data structures, and then your complexity problems will take care of themselves. In this case, it means using something like Data.Vector.Unboxed to represent the sequence, which has O(1) indexing, as well as low constant overheads in general.


With nothing more than a really mindless wrapping of your lists into Vectors I get 2.5 seconds when the input list is [1..10000].

import qualified Data.Vector as V
import Data.Vector (Vector, (!))

main = print $ liss [0..10000]

liss :: [Int] -> Int
liss seqL = V.maximum dp
    where dp = V.fromList $ 1 : [val n | n <- [1..length seqL - 1]]
          seq = V.fromList seqL
          val n = (1 +) . filter'and'get'max ((<= top) . (seq!)) $ [0..pred n]
            where top = seq!n
          -----
          filter'and'get'max :: (Int -> Bool) -> [Int] -> Int
          filter'and'get'max f []     = 0
          filter'and'get'max f [x]    = if f x then dp!x else 0
          filter'and'get'max f (x:xs) = if f x then ( if vx > vxs then vx else vxs ) else vxs
            where vx  = dp!x
                  vxs = filter'and'get'max f xs

The compilation and execution:

tommd@Mavlo:Test$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.0.3
tommd@Mavlo:Test$ ghc -O2 so.hs
[1 of 1] Compiling Main             ( so.hs, so.o )
Linking so ...
tommd@Mavlo:Test$ time ./so
10001

real    0m2.536s
user    0m2.528s

A worker-wrapper transformation on filter'and'get'max seems to shave off another second.

Also, I don't understand why you need that middle case (filter'and'get'max f [x]), shouldn't it work fine without that? I guess this changes the result if dp!x < 0. Note eliminating that saves 0.3 seconds right there.

And the python code you provided takes ~ 10.7 seconds (added a call of longest(range(1,10000));).

tommd@Mavlo:Test$ time python so.py

real    0m10.745s
user    0m10.729s
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号