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

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

我最近编写了一些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 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..]