我实现了KMP算法的失败表。
kmp s = b
where a = listArray (0,length s-1) s
b = 0:list 0 (tail s)
list _ [] = []
list n (x:xs)
| x==a!n = (n+1):list (n+1) xs
| n > 0 = list (b!!(n-1)) (x:xs)
| otherwise = 0:list 0 xs
b
是一个列表,最后一行中的b!!(n-1)
很慢,因此我希望加快速度,并执行以下操作。
kmp s = b
where a = listArray (0,length s-1) s
t = listArray (0,length s-1) b
b = 0:list 0 (tail s)
list _ [] = []
list n (x:xs)
| x==a!n = (n+1):list (n+1) xs
| n > 0 = list (t!(n-1)) (x:xs)
| otherwise = 0:list 0 xs
请注意,唯一的区别是将b!!
替换为t!
,并声明t
为从b
生成的数组。
对于相同的输入,原始代码具有正确的输出,但新的输出只输出<<loop>>
。
如何解决这个问题?
答案 0 :(得分:3)
您的问题是列表b
需要数组t
来确定其结构(长度)。但是数组t
在存在之前需要列表的长度:
listArray :: Ix i => (i,i) -> [e] -> Array i e
listArray (l,u) es = runST (ST $ \s1# ->
case safeRangeSize (l,u) of { n@(I# n#) ->
case newArray# n# arrEleBottom s1# of { (# s2#, marr# #) ->
let fillFromList i# xs s3# | i# ==# n# = s3#
| otherwise = case xs of
[] -> s3#
y:ys -> case writeArray# marr# i# y s3# of { s4# ->
fillFromList (i# +# 1#) ys s4# } in
case fillFromList 0# es s2# of { s3# ->
done l u n marr# s3# }}})
正如您所看到的,首先分配一个适当大小的原始数组,然后填充arrEleBottom
(这是一个带有消息未定义数组元素的error
调用),然后遍历列表并且列表元素被写入数组(列表元素可以毫无问题地引用数组值)。然后,最后,阵列被冻结。在数组被冻结之前,无法从填充代码外部访问它(它基本上是ST s
计算)。
修复它的最简单方法是IMO在ST s
monad中使用可变数组,
kmp s = elems b
where
l = length s - 1
a = listArray (0, l) s
b = runSTArray $ do
t <- newArray_ (0,l)
writeArray t 0 0
let fill _ _ [] = return t
fill i n (x:xs)
| x == a!n = do
writeArray t i (n+1)
fill (i+1) (n+1) xs
| n > 0 = do
k <- readArray t (n-1)
fill i k (x:xs)
| otherwise = do
writeArray t i 0
fill (i+1) 0 xs
fill 1 0 (tail s)
是代码的非常直接(并且效率低下)的翻译。
答案 1 :(得分:1)
这并不直接回答您的问题,但提供的修正比使用STArray
的建议版本更简单。
该算法的命令式版本直接转换为不使用重复列表索引或状态的版本,只是惰性数组构造:
import Data.Array.IArray
kmp :: String -> Array Int Int
kmp s = b
where
a :: Array Int Char
a = listArray (0,l-1) s
b = listArray (1,l) (0:map (\q -> f (b!(q-1)) q) [2..l])
f k q
| k > 0 && (a ! k) /= (a ! (q-1)) =
f (b ! k) q
| a ! k == a ! (q-1) = k + 1
| otherwise = k
l = length s
但是,我没有对此进行基准测试。