String 有效的大规模字符串搜索问题
问题:提供了大量静态字符串列表。由数据和通配符元素(*和?)组成的模式字符串。其思想是返回与模式匹配的所有字符串-足够简单 当前解决方案:我目前正在使用一种线性方法扫描大列表,并根据模式对每个条目进行全局搜索 我的问题:是否有合适的数据结构可以将大列表存储到其中,以使搜索的复杂性小于O(n)String 有效的大规模字符串搜索问题,string,search,design-patterns,dfa,glob,String,Search,Design Patterns,Dfa,Glob,问题:提供了大量静态字符串列表。由数据和通配符元素(*和?)组成的模式字符串。其思想是返回与模式匹配的所有字符串-足够简单 当前解决方案:我目前正在使用一种线性方法扫描大列表,并根据模式对每个条目进行全局搜索 我的问题:是否有合适的数据结构可以将大列表存储到其中,以使搜索的复杂性小于O(n) 也许是类似于后缀trie的东西?我也曾考虑过在哈希表中使用bi和tri-gram,但基于返回的单词列表和模式的合并来评估匹配所需的逻辑是一场噩梦,而且我不相信这是正确的方法。如果你不在乎内存,你可以负担预处
也许是类似于后缀trie的东西?我也曾考虑过在哈希表中使用bi和tri-gram,但基于返回的单词列表和模式的合并来评估匹配所需的逻辑是一场噩梦,而且我不相信这是正确的方法。如果你不在乎内存,你可以负担预处理列表的费用,创建每个后缀的排序数组,指向原始单词,例如,对于['hello','world',],存储以下内容:
[('d' , 'world'),
('ello' , 'hello'),
('hello', 'hello'),
('ld' , 'world'),
('llo' , 'hello'),
('lo' , 'hello'),
('o' , 'hello'),
('orld' , 'world'),
('rld' , 'world'),
('world', 'world')]
使用此数组可以使用模式片段构建候选匹配集
例如,如果模式是*或*
,则在子字符串或
上使用二进制印章查找候选匹配('orld','world')
,然后使用常规全局搜索方法确认匹配
如果通配符更复杂,例如,
h*o
,则为h
和o
构建候选集,并在最终线性全局之前找到它们的交点。您可以构建常规trie并添加通配符边。那么您的复杂性将是O(n),其中n是模式的长度。您必须首先在模式中将**
的运行替换为*
(也是一个O(n)操作)
如果列表中的单词是“我是一头牛”,那么trie看起来有点像这样:
(I ($ [I])
a (m ($ [am])
n ($ [an])
? ($ [am an])
* ($ [am an]))
o (x ($ [ox])
? ($ [ox])
* ($ [ox]))
? ($ [I]
m ($ [am])
n ($ [an])
x ($ [ox])
? ($ [am an ox])
* ($ [I am an ox]
m ($ [am]) ...)
* ($ [I am an ox]
I ...
...
(I($[I])
a(米($[am])
n($[an])
($[am an])
*($[am an]))
o(x($[ox])
($[ox])
*($[ox]))
($[I]
m($[am])
n($[an])
x($[ox])
($[我是一头牛])
*($[我是一头牛]
m($[am])…)
*($[我是一头牛]
我
...
下面是一个示例python程序:
import sys
def addWord(root, word):
add(root, word, word, '')
def add(root, word, tail, prev):
if tail == '':
addLeaf(root, word)
else:
head = tail[0]
tail2 = tail[1:]
add(addEdge(root, head), word, tail2, head)
add(addEdge(root, '?'), word, tail2, head)
if prev != '*':
for l in range(len(tail)+1):
add(addEdge(root, '*'), word, tail[l:], '*')
def addEdge(root, char):
if not root.has_key(char):
root[char] = {}
return root[char]
def addLeaf(root, word):
if not root.has_key('$'):
root['$'] = []
leaf = root['$']
if word not in leaf:
leaf.append(word)
def findWord(root, pattern):
prev = ''
for p in pattern:
if p == '*' and prev == '*':
continue
prev = p
if not root.has_key(p):
return []
root = root[p]
if not root.has_key('$'):
return []
return root['$']
def run():
print("Enter words, one per line terminate with a . on a line")
root = {}
while 1:
line = sys.stdin.readline()[:-1]
if line == '.': break
addWord(root, line)
print(repr(root))
print("Now enter search patterns. Do not use multiple sequential '*'s")
while 1:
line = sys.stdin.readline()[:-1]
if line == '.': break
print(findWord(root, line))
run()
导入系统
def addWord(根,字):
添加(词根、单词、单词“”)
def添加(根、字、尾、上一个):
如果tail='':
addLeaf(根,字)
其他:
头=尾[0]
tail2=tail[1:]
添加(添加边缘(根、头)、字、尾2、头)
添加(添加边(根“?”)、字、尾2、头)
如果上一个!='*':
对于范围内的l(长(尾)+1):
add(addEdge(根“*”),word,tail[l:],“*”)
def addEdge(根,字符):
如果不是root.has_键(char):
根[char]={}
返回根[char]
def addLeaf(根,字):
如果不是root.has_键(“$”):
根['$']=[]
叶=根['$']
如果单词不在叶中:
追加(单词)
def findWord(根,模式):
上一页=“”
对于模式中的p:
如果p='*'和prev='*':
持续
prev=p
如果不是root.has_key(p):
返回[]
根=根[p]
如果不是root.has_键(“$”):
返回[]
返回根['$']
def run():
打印(“输入单词,每行一个,以一行上的.结尾”)
根={}
而1:
line=sys.stdin.readline()[:-1]
如果行=='。:中断
addWord(根,行)
打印(报告(根))
打印(“现在输入搜索模式。不要使用多个连续的“*”)
而1:
line=sys.stdin.readline()[:-1]
如果行=='。:中断
打印(findWord(根,行))
运行()
我同意后缀trie是一个不错的尝试,除了数据集的巨大规模可能会使它的构建占用与其使用所能节省的时间一样多的时间。如果你必须多次查询它们以分摊构建成本,这是最好的选择。可能是几百次查询
还请注意,这是并行性的一个很好的借口。将列表一分为二,交给两个不同的处理器,让您的工作完成得更快一倍。您说您目前正在进行线性搜索。这是否为您提供了有关最常执行的查询模式的数据?例如,
blah*
比bl?h (我认为是)在你当前的用户中
有了这种先验知识,您可以将索引工作重点放在常用案例上,并将它们降到O(1),而不是试图解决使每一个可能的查询都同样快速这一更难但却不太值得的问题。您可以通过保持字符串中的字符计数来实现简单的加速。没有b
s或单个b
的字符串永远无法匹配查询abba*
,因此存在no测试它的要点。如果你的字符串是由这些词组成的,那么这对整个词的效果要好得多,因为单词比字符多得多;另外,有很多库可以为你建立索引。另一方面,它与你提到的n-gram方法非常相似
如果您不使用为您执行此操作的库,则可以通过在索引中首先查找最不常用的全局字符(或单词或n-gram)来优化查询。这允许您提前丢弃更多不匹配的字符串
一般来说,所有的加速都是基于丢弃不可能匹配的东西的想法。索引的内容和数量取决于您的数据。例如,如果典型的模式长度接近字符串长度,您可以简单地检查字符串是否足够长以容纳模式。有很多很好的算法可用于索引多字符串搜索。谷歌搜索“纳瓦罗字符串搜索”,您将看到对多字符串选项的良好分析。许多算法在“正常”情况下非常适用(搜索相当长的字符串:Wu Manber;搜索字符串时使用的字符在