2

我实现了 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>>

如何解决这个问题?

4

2 回答 2

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计算)。

ST s修复它的最简单方法是 IMO 在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)

是对代码的一种非常直接(而且效率有点低)的翻译。

于 2012-12-03T03:40:14.707 回答
1

这并不能直接回答您的问题,但提供了比使用STArrays.

该算法的命令式版本直接转换为不使用重复列表索引或状态的版本,只是惰性数组构造:

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

但是,我没有对此进行基准测试。

于 2012-12-03T06:49:09.753 回答