Haskell 如何查找具有开始和结束索引的字符串的所有子字符串
我最近编写了一些Scala代码,它处理一个字符串,查找它的所有子字符串,并保留字典中的子字符串列表。整个字符串中子字符串的开头和结尾也必须保留以供以后使用,因此最简单的方法似乎就是使用嵌套for循环,如下所示:Haskell 如何查找具有开始和结束索引的字符串的所有子字符串,haskell,Haskell,我最近编写了一些Scala代码,它处理一个字符串,查找它的所有子字符串,并保留字典中的子字符串列表。整个字符串中子字符串的开头和结尾也必须保留以供以后使用,因此最简单的方法似乎就是使用嵌套for循环,如下所示: for (i <- 0 until word.length) for (j <- i until word.length) { val sub = word.substring(i, j + 1) // lookup sub in dictionary h
for (i <- 0 until word.length)
for (j <- i until word.length) {
val sub = word.substring(i, j + 1)
// lookup sub in dictionary here and add new match if found
}
tokens "blah"
[("b",0,0),("bl",0,1),("bla",0,2),("blah",0,3),("l",1,1),("la",1,2),("lah",1,3),("a",2,2),("ah",2,3),("h",3,3)]
鉴于我对哈斯克尔的了解有限,这似乎相对容易理解
import Data.List
continuousSubSeqs = filter (not . null) . concatMap inits . tails
tokens xs = map (\(s, l) -> (s, head l, last l)) $ zip s ind
where s = continuousSubSeqs xs
ind = continuousSubSeqs [0..length(xs)-1]
工作原理如下:
for (i <- 0 until word.length)
for (j <- i until word.length) {
val sub = word.substring(i, j + 1)
// lookup sub in dictionary here and add new match if found
}
tokens "blah"
[("b",0,0),("bl",0,1),("bla",0,2),("blah",0,3),("l",1,1),("la",1,2),("lah",1,3),("a",2,2),("ah",2,3),("h",3,3)]
我的版本:
import Data.List
tokens =
map join . filter (not . null) . concatMap inits . tails . zip [0..]
where
join s@((i, _):t) =
(map snd s, i, foldl' (\_ i -> i) i (map fst t))
main =
putStrLn $ show $ tokens "blah"
-- [("b",0,0),("bl",0,1),("bla",0,2),("blah",0,3),("l",1,1),("la",1,2),("lah",1,3),("a",2,2),("ah",2,3),("h",3,3)]
更新:
import Control.Arrow
...
tokens =
map join . filter (not . null) . concatMap inits . tails . zip [0..] where
join s = (s', i, j) where
((i, j), s') = (first (head &&& last)) $ unzip s
...
您编写的两个嵌套循环是一个很好的起点。也就是说,我们可以编写一个函数
tokens
,将其工作委托给两个递归函数outer
和inner
,这两个函数对应于您的循环:
type Token a = ([a], Int, Int)
tokens :: [a] -> [Token a]
tokens = outer 0
where
outer _ [] = []
outer i l@(_ : xs) = inner i [] l ++ outer (i + 1) xs
where
inner _ _ [] = []
inner j acc (x : xs) =
(acc ++ [x], i, j) : inner (j + 1) (acc ++ [x]) xs
在这里,outer
对字符串进行迭代,对于该字符串中的每个起始位置,调用internal
以收集从该位置开始的所有段及其结束位置
虽然此功能满足您的要求
> tokens "blah"
[("b",0,0),("bl",0,1),("bla",0,2),("blah",0,3),("l",1,1),("la",1,2),("lah",1,3),("a",2,2),("ah",2,3),("h",3,3)]
由于重复的列表连接,这是非常低效的。更有效的版本会将其结果累积到所谓的:
当然,如何构建词典取决于您选择如何表示它。这是一种使用简单有序关联列表的方法
type Dict a = [([a], [(Int, Int)])]
empty :: Dict a
empty = []
update :: Ord a => Token a -> Dict a -> Dict a
update (xs, i, j) [] = [(xs, [(i, j)])]
update (xs, i, j) ((ys, ns) : dict) = case compare xs ys of
LT -> (xs, [(i, j)]) : (ys, ns) : dict
EQ -> (ys, (i, j) : ns) : dict
GT -> (ys, ns) : update (xs, i, j) dict
toDict :: Ord a => [a] -> Dict a
toDict = foldr update empty . tokens
但是,由于键是字符串,(又称前缀树)可能是更好的选择
如果您所追求的是高效的子字符串查询,我建议您进行研究,尽管它们的实现有些复杂。你可能想退房
- 罗伯特·吉格里奇和斯特凡·库尔茨。命令式和纯函数式后缀树结构的比较。计算机编程科学25(2-3):187-2181995
还有Bryan O'Sullivan的Hackage软件包。另一个版本更容易从左到右阅读,类似于unix管道
import Data.List
import Control.Category
tokens =
tailsWithIndex
>>> concatMap (\(i,str) -> zip (repeat i) (initsWithIndex str))
>>> map adjust
where
tailsWithIndex = tails >>> init >>> zip [0..]
initsWithIndex = inits >>> tail >>> zip [0..]
adjust (i, (j, str)) = (str, i, i+j)
样本运行
>tokens "blah"
[("b",0,0),("bl",0,1),("bla",0,2),("blah",0,3),("l",1,1),("la",1,2),("lah",1,3),("a",2,2),("ah",2,3),("h",3,3)]
如果
concatMap
是惰性的,那么整个计算是惰性的,并且将是高效的,除了使用Data.List函数而不是原始列表访问。您可以跳过长度部分;使用infinite list[0..]
也同样有效。嗯,我想现在我想不会了。(我认为子序列是按照[“a”、“b”、“ab”、“c”、“bc”、“abc”、“d”…]的顺序生成的。
如果使用concatMap tails,那么无限序列就可以工作了)。inits
,则可以跳过长度,只需使用[0..]
)