我正在努力使功能更有效但我已经做得最差,我无法理解为什么。有人能看出原因并向我解释一下吗?
原始功能:
substringsSB s = substringsSB' Set.empty s
substringsSB' m s = substrings' m s
where
substrings' m s = {-# SCC "substrings'" #-}if (Set.member s m) then m else foldl' insertInits m (init . B.tails $ s)
insertInits m s = {-# SCC "insertInits" #-}if (Set.member s m) then m else foldl' doInsert m (tail . B.inits $ s)
doInsert m k = {-# SCC "doInsert" #-}Set.insert k m
分析结果:
total time = 3.14 secs (157 ticks @ 20 ms)
total alloc = 1,642,067,360 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
doInsert Main 95.5 92.1
insertInits Main 2.5 7.8
substringsSB' Main 1.9 0.0
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 1 0 0.0 0.0 100.0 100.0
main Main 280 1 0.0 0.0 100.0 100.0
substringsSB Main 281 1 0.0 0.0 100.0 100.0
substringsSB' Main 282 1 1.9 0.0 100.0 100.0
doInsert Main 285 1233232 95.5 92.1 95.5 92.1
insertInits Main 284 1570 2.5 7.8 2.5 7.8
substrings' Main 283 1 0.0 0.0 0.0 0.0
CAF GHC.IO.Handle.FD 211 3 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 169 2 0.0 0.0 0.0 0.0
CAF GHC.Conc.Signal 166 1 0.0 0.0 0.0 0.0
据我所知,我们不能在 fold foldl
中提前退出,因此该函数可能会花费大量时间来调用Set.member s m
并返回m
中的substrings'
。所以,我将函数转换为使用递归:
substringsSB s = substringsSB' Set.empty s
substringsSB' m str = substrings' m (init . B.tails $ str)
where
substrings' m [] = m
substrings' m (s:ss) | Set.member s m = m
| otherwise = {-# SCC "substrings'" #-}substrings' insertTail ss
where insertTail = insertInits m $ reverse $ (tail . B.inits $ s)
insertInits m [] = m
insertInits m (s:ss) | Set.member s m = m
| otherwise = {-# SCC "insertInits" #-}insertInits (doInsert s m) ss
doInsert k m = {-# SCC "doInsert" #-}Set.insert k m
分析结果:
total time = 5.16 secs (258 ticks @ 20 ms)
total alloc = 1,662,535,200 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
doInsert Main 54.7 90.5
substringsSB' Main 43.8 9.5
insertInits Main 1.6 0.0
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 1 0 0.0 0.0 100.0 100.0
main Main 280 1 0.0 0.0 100.0 100.0
substringsSB Main 281 1 0.0 0.0 100.0 100.0
substringsSB' Main 282 1 43.8 9.5 100.0 100.0
doInsert Main 285 1225600 54.7 90.5 54.7 90.5
insertInits Main 284 1225600 1.6 0.0 1.6 0.0
substrings' Main 283 1568 0.0 0.0 0.0 0.0
CAF GHC.IO.Handle.FD 211 3 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 169 2 0.0 0.0 0.0 0.0
CAF GHC.Conc.Signal 166 1 0.0 0.0 0.0 0.0
但这需要比原始版本更多的时间。
为什么在substringsSB'
花了这么多时间?
它仅执行init . B.tails $ str
,原始版本也称之为...
或者我犯了一个错误,这两个函数在逻辑上是不相同的吗?
main = do
s <- getLine
let m = substringsSB $ B.pack s
print $ Set.size m
return ()
输入:
asjasdfkjasdfjkasdjlflaasdfjklajsdflkjasvdadufhsaodifkljaiduhfjknhdfasjlkdfndbhfisjglkasnjjfgklsadmsjnhsjdflkmsnajjkdlsmfnjsdkfljasd;fjlkasdjfklasjdfnasdfjjnsadfjsadfhasjdfjlaksdfjlkasdfjljkasdflasidfjlaisjdflaisdjflaisjdfliasjdgfouqhagdfsia;klsjdfnklajsdfkhkasfhjdasdfhaskdflhjaklsdfh;kjlasdfh;jlaksdflkhajsdfkjahsdfkjhasdfkkasdfkjlkasfdkljasdfkhljkasdkflkjasdfasdlfkajsdlfkjaslkdfjjaksdjgujhgjhghjbjnbghjghhgfghfghvfgfgjhgjhdfjfjhgfjgvjhgvjhgvjhgvjhgvjhgvjhasdkfjkasdjfklajsdfklkahsdfjklhjklhghjhkhgfvcghjkjhghjkjhhvjkl/ljklkjlkjlkjlkjaslkdfjasd;lkfjas;dlfkjas;dflkjas;dflkjas;dflkjas;dflkja;slkdfja;sdlkjfa;sdlkfja;lsdfkjas;ldkfja;sdlkfja;skldfja;slkdjfa;slkdfja;sdklfjas;dlkfjas;dklfjas;dlkfjas;dfkljas;dflkjas;lkdfja;sldkfj;aslkdfja;sldkfja;slkdfj;alksdjf;alsdkfj;alsdkfja;sdflkja;sdflkja;sdlfkja;sdlfkja;sldkfja;sdlkfja;sldfkj;asldkfja;sldkfja;lsdkfja;sldfkja;sdlfjka;sdlfjkas;dlkfjas;ldkfjas;dlfkjasfd;lkjasd;fljkads;flkjasdf;lkjasdf;lkajsdf;lkajsdf;aksljdf;alksjdfa;slkdjfa;slkdjfa;slkdfja;sdflkjas;dflkjasd;flkjasd;flkjasdf;lkjasdf;ljkasdf;lkajdsf;laksjf;asldfkja;sdfljkads;flkjasd;fljkasdf;lkjasdf;ljkadfs;fljkadfs;ljkasdf;lajksdf;lkajsdf;lajsfd;laksdfgvjhgvjhgvjhcfjhgcjfgvjkgvjjgfjghfhgkhkjhbkjhbkjhbkybkkugtkydfktyufctkyckxckghfvkuygjkhbykutgtvkyckjhbliuhgktuyfkvuyjbjkjygvkuykjdjflaksdjflkajsdlkfjalskdjflkasjdflkjasdlkfjalksdjfklajsdflkjasdlkjfalksdjflkasjdflkjasdlfkjaslkdjflaksjdflkajsdlfkjasdlkfjalsdjflkasjdflkasjdflajsdfjsfuhaduvasdyhaweuisfnaysdfiuhasfdnhaksjdfahsdfiujknsadfhbaiuhdfjknahbdshfjksnashdfkjnsadfiukjfnhsdfkjnasdfikjansdfhnaksdjfaisdfkn
答案 0 :(得分:1)
可悲的是,Set.member
也很昂贵。
在第一个版本中,如果之前已经看过,则检查每个尾部,如果是,则忽略它,否则插入所有非空的inits。如果输入足够不规则,即O(n)成员资格测试和O(n ^ 2)插入,则总共为O(n ^ 2 * log n)(假设比较的平均成本为O(1))。如果输入是周期性的,具有最短(正)周期p,则只有第一个p尾导致插入,因此O(n)测试和O(p * n)插入,O(p * n * log n)整体(这是有点作弊,比较的平均成本可以达到O(p)如果p> 1而O(n)如果p == 1,但如果周期本身是不规则的,那么比较的O(1)是可以的)。
第二,
substringsSB s = substringsSB' Set.empty s
substringsSB' m str = substrings' m (init . B.tails $ str)
where
substrings' m [] = m
substrings' m (s:ss) | Set.member s m = m
| otherwise = substrings' insertTail ss
where
insertTail = insertInits m $ reverse $ (tail . B.inits $ s)
如果之前已经看过,你会检查每个尾巴,如果是这样的话。这很好,但是没有超过第一个。在第一个中,如果之前已经看过一个尾部,之前也看到了所有进一步的尾部,所以你只能跳过大多数O(n)隶属度测试,O(n *记录n)操作。对于通常不规则的输入,之前只看到了一些最短的尾部,所以只跳过很少的测试 - 很少获得。
insertInits m [] = m
insertInits m (s:ss) | Set.member s m = m
| otherwise = insertInits (doInsert s m) ss
doInsert k m = {-# SCC "doInsert" #-}Set.insert k m
如果还没有看到尾巴(正常),你开始插入它的内容 - 从最长到最短 - 如果之前已经看过任何东西(因为之后也看到了所有较短的内容)。如果很多长时间出现多次,这很好,但如果没有,你只需要进行O(n ^ 2)次会员测试。
对于普通的不规则输入,没有长的子串出现多次,但是很多短的子串都会出现,并且保存的少量插入不会补偿额外的成员资格测试,使第二种方法的速度变慢。 (成员资格测试比插入更便宜,因此该因子应小于2.)
对于周期性输入,第一种方法也避免了不必要的插入,第二种方法在外部循环中保存了O(n)测试,但在内部循环中添加了O(p * n)测试,使得它比不规则的更差情况下。
但对于某些输入,第二种方法可以更好。尝试两者
main = do
let x = substringsSB $ B.pack $ replicate 9999 97 ++ [98]
print (Set.size x)
您可以通过在插入之后使用便宜的member
比较替换插入之前的昂贵size
来改进第二个版本,
substringsSB str = go 0 Set.empty (init $ B.tails str)
where
go sz m (s:ss)
| Set.member s m = m
| otherwise = go nsz nm ss
where
(nsz,nm) = insInits sz m (reverse . tail $ B.inits s)
go _ m [] = m
insInits sz m (s:ss)
| sz1 == sz = (sz,m)
| otherwise = insInits sz1 nm ss
where
nm = Set.insert s m
sz1 = Set.size nm
insInits sz m [] = (sz,m)
这使得它接近通用案例中的第一个版本,使它(比这里)略好于concat $ replicate n "abcde"
的第一个版本,并且对于上面的邪恶示例更好。