我在haskell中编写了indexOf函数。有更好的书写方式吗? 我的第二个问题是 在我的函数中,尾函数是惰性计算的吗?
以下是我的功能索引
import Data.List
indexof :: String -> String -> Int
indexof lat patt = helper (tails lat) 0
where helper [] _ = -1
helper (x:xs) a = if prefix x patt then a else helper xs (a + 1)
prefix :: String -> String -> Bool
prefix _ [] = True
prefix [] _ = False
prefix (x:xs) (y:ys) = if x == y then prefix xs ys else False
这按预期工作。
答案 0 :(得分:4)
使用patt
ern作为第一个参数看起来更惯用,通常无法通过-1
或其他某个值来解决故障,而是通过使用Nothing
并因此使用{{1 }}作为返回类型。
我们可以在此处使用Maybe Int
模式,使其更加优雅,foldr
具有isPrefixOf :: Eq a => [a] -> [a] -> Bool
:
Data.List
不过,最好实施Knuth-Morris-Pratt algorithm [wiki],因为这将导致在 O(m + n)中使用模式长度的 m 搜索和 n 字符串的长度。当前方法要求 O(m×n)。
我的第二个问题是
import Data.List(isPrefixOf, tails) indexof :: Eq a => [a] -> [a] -> Maybe Int indexof patt = foldr helper Nothing . tails where helper cur rec | isPrefixOf patt cur = Just 0 | otherwise = fmap (1+) rec
函数是否被延迟计算?
tails
确实是懒惰的评估。但是,瓶颈可能不在tails :: [a] -> [[a]]
中,因为tails
在评估列表的 O(n)中运行,并且需要 O(n)内存也一样,因为tails
指针是共享的。因此,它实际上并没有为每个项目构造一个新的列表,它只是在每个时间指向上一个元素的尾部。
答案 1 :(得分:2)
建立在Willem的答案上:通常通过使用[0..]
压缩来跟踪索引。这里的方法是找到一个可能匹配的列表[Maybe Int]
,然后选择第一个(当然,这都是偷懒完成的,因此我们从来没有真正计算出第一个Just
之后的匹配列表发生)。
indexOf :: (Eq a) => [a] -> [a] -> Maybe Int
indexOf needle haystack = firstJust $ zipWith findmatch [0..] (tails haystack)
where
findmatch ix suffix
| needle `isPrefixOf` suffix -> Just ix
| otherwise -> Nothing
firstJust :: [Maybe a] -> Maybe a
firstJust = getFirst . mconcat . map First
-- N.B. should really use `coerce` instead of `map First`
我觉得这很“直接”,我喜欢。我们可以更聪明地减少代码大小:
indexOf needle haystack = listToMaybe . concat $ zipWith findmatch [0..] (tails haystack)
where
findmatch ix suffix = [ ix | needle `isPrefixOf` suffix ]
本质上,我们使用零元素列表或一元素列表来模拟Maybe
,然后使用稍微更好的库和列表的符号支持来发挥我们的优势。这也可能融合得很好...(我对此没有很好的直觉)
但是,是的,如果您希望它更快,则应该使用KMP(在Text
而不是String
上)。但是,它涉及的更多。