如何查找具有开始和结束索引的String的所有子字符串

时间:2012-07-15 21:44:37

标签: haskell

我最近编写了一些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知识,这似乎相对容易理解。

4 个答案:

答案 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,将其工作委托给两个与你的循环相对应的递归函数outerinner

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,尽管它们的实现有些牵连。你可能想看看

  • Robert Giegerich和Stefan Kurtz。命令式和纯函数后缀树结构的比较。 计算机程序设计科学 25(2-3):187-218,1995

和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函数而不是原始列表访问。