生成此数组时无限循环

时间:2012-12-03 03:05:33

标签: haskell

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

如何解决这个问题?

2 个答案:

答案 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

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

相关问题