我最近编写了一些Scala代码,用于处理String,查找其所有子字符串并保留字典中的字符串列表。整个字符串中子字符串的开头和结尾也必须保留供以后使用,所以最简单的方法就是使用嵌套for循环,如下所示:
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
}
作为一项练习,我决定在Haskell中做同样的事情。在不需要子字符串索引的情况下看起来很简单 - 我可以使用this approach之类的东西来获取子字符串,然后调用递归函数来累积匹配。但如果我也想要指数,那似乎更棘手。
我如何编写一个函数,该函数返回包含每个连续子字符串的列表及其在“父”字符串中的起始和结束索引?
例如tokens "blah"
会给[("b",0,0), ("bl",0,1), ("bla",0,2), ...]
大量的答案和许多值得探索的新事物。在搞砸了一下之后,我已经找到了第一个答案,丹尼尔建议允许使用[0..]
。
data Token = Token String Int Int
continuousSubSeqs = filter (not . null) . concatMap tails . inits
tokenize xs = map (\(s, l) -> Token s (head l) (last l)) $ zip s ind
where s = continuousSubSeqs xs
ind = continuousSubSeqs [0..]
鉴于我有限的Haskell知识,这似乎相对容易理解。
答案 0 :(得分:2)
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]
像这样工作:
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)]
答案 1 :(得分:1)
我的版本:
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
...
答案 2 :(得分:1)
你写的两个嵌套循环是一个很好的起点。也就是说,我们可以编写一个函数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
遍历字符串,并且对于该字符串中的每个起始位置,调用inner
以收集从该位置开始的所有段及其结束位置。
虽然此功能符合您的要求,但
> 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)]
由于重复列表连接,效率非常低。更有效的版本会将其结果累积在所谓的difference lists:
中type Token a = ([a], Int, Int)
tokens :: [a] -> [Token a]
tokens l = outer 0 l []
where
outer _ [] = id
outer i l@(_ : xs) = inner i id l . outer (i + 1) xs
where
inner _ _ [] = id
inner j acc (x : xs) =
((acc [x], i, j) :) . inner (j + 1) (acc . (x :)) xs
如何构建字典当然取决于您选择如何表示它。这是一种使用简单有序关联列表的方法,
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
但由于您的密钥是字符串,tries(a.k.a。前缀树)可能是更好的选择。
如果它是您所追求的有效子字符串查询,我建议您查看suffix trees,尽管它们的实现有些牵连。你可能想看看
和Bryan O'Sullivan关于Hackage的suffixtree包。
答案 3 :(得分:1)
另一个版本从左到右更容易阅读,类似于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函数而不是原始列表访问。