在单词词典中提取以片段开头/包含/结束的单词
Fetch Words Starting/Containing/Ending With Fragment in a Word Dictionary
假设我们有一个来自英语词典a-Z的所有词典单词的列表。
我有三个案例要处理这些单词列表:
1) 找出所有"以开头"的单词——一个特定的片段
eg: If my fragment is 'car', word 'card' should be returned
2) 找出"包含"的所有单词,将片段作为子字符串
eg: If my fragment is 'ace', word 'facebook' should be returned
3) 找出所有"以结尾"的单词——一个特定的片段
eg: If my fragment is 'age', word 'image' should be returned
在网上进行了一些搜索后,我发现1)可以通过trie/compressed trie完成,3)可以通过后缀树完成。
我不确定怎样才能实现。此外,还有什么更好的方案可以处理这三种情况吗?由于维护前缀和后缀树可能是一项内存密集型任务。
请让我知道任何其他需要注意的领域。
提前谢谢。
PS:我将使用C++来实现这个
第1版:目前,我在这里的巨大帮助下构建了一个后缀树。
C语言中的单词后缀树生成
在这里,我需要为整个英语词典单词构建一个后缀树。那么我应该创建一个吗
a)每个单词的独立后缀树或
b)为所有单词创建一个通用后缀树。
我不知道如何在a)情况中的子串匹配时跟踪每个单词的单个树
有指针吗?
正如我在一条评论中指出的,前缀和后缀的大小写由一般的子字符串大小写(#2)覆盖。根据定义,所有前缀和后缀也是子字符串。所以我们要解决的就是一般的子串问题。
由于您有一个静态字典,因此可以相对容易地将其预处理为快速查询子字符串的形式。您可以使用后缀树来实现这一点,但构造和处理简单排序的平面数据向量要容易得多,所以这就是我将在这里描述的。
那么,最终目标是有一个经过排序的子单词列表,这样就可以进行二进制搜索来找到匹配的单词。
首先,观察到,为了找到与查询片段匹配的最长子串,不需要列出每个单词的所有可能的子串,而只需要列出所有可能的后缀;这是因为所有子字符串都可以仅仅看作后缀的前缀。(明白了吗?第一次遇到它有点让人费解,但最终很简单,非常有用。)
因此,如果你生成每个词典单词的所有后缀,然后对它们进行排序,你就可以在任何词典单词中找到任何特定的子字符串:对后缀进行二进制搜索,找到下限(std::lower_bound
)——第一个以查询片段开头的后缀。然后找到上限(std::upper_bound
)——这将是以查询片段开头的最后一个后缀之后的一个。范围[lower,upper]中的所有后缀都必须以查询片段开头,因此这些后缀最初来自的所有单词都包含查询片段
现在,很明显,实际上拼写出所有的后缀需要大量的内存,但你不需要。后缀可以被认为只是一个单词的索引,也就是后缀开始的偏移量。因此,每个可能的后缀只需要一对整数:一个用于(原始)单词索引,一个用于该单词中后缀的索引。(你可以根据字典的大小巧妙地将这两个组合在一起,以节省更多的空间。)
总之,你所需要做的就是:
- 为所有单词生成一个包含所有单词后缀索引对的数组
- 根据它们的语义将它们作为后缀(而不是数值)进行排序。我建议
std::stable_sort
带有自定义比较器。这是最长的一步,但可以离线完成一次,因为您的字典是静态的 - 对于给定的查询片段,在排序后的后缀索引中找到下限和上限。该范围中的每个后缀都对应于一个匹配的子字符串(查询长度,从单词索引处单词的后缀索引开始)。请注意,有些单词可能匹配不止一次,而且匹配甚至可能重叠
为了澄清,这里有一个由单词"臭鼬"answers"奶酪"组成的词典的小例子。
"skunk"的后缀是"skung"、"kunk"、"unk"、"nk"answers"k"。用指数表示,它们是0, 1, 2, 3, 4
。"cheese"的后缀是"cheese"、"heese"、"eese"、"ese"、"se"answers"e"。指数为CCD_ 5。
由于"skunk"是我们非常有限的假想字典中的第一个单词,我们将为其指定索引0。"奶酪"在索引1中。所以最后的后缀是:0:0, 0:1, 0:2, 0:3, 0:4, 1:0, 1:1, 1:2, 1:3, 1:4, 1:5
。
对这些后缀进行排序会产生以下后缀字典(我添加了实际对应的文本子字符串,仅供说明):
0 | 0:0 | cheese
1 | 0:5 | e
2 | 0:2 | eese
3 | 0:3 | ese
4 | 0:1 | heese
5 | 1:4 | k
6 | 1:1 | kunk
7 | 1:3 | nk
8 | 0:4 | se
9 | 1:0 | skunk
10 | 1:2 | unk
考虑查询片段"e"。下限是1,因为"e"是第一个大于或等于查询"e"的后缀。上限是4,因为4("heese")是第一个大于"e"的后缀。因此,1、2和3处的后缀都以查询开头,因此它们来自的单词都包含作为子字符串的查询(在后缀索引处,表示查询的长度)。在这种情况下,这三个后缀都属于"cheese",偏移量不同。
请注意,对于不是任何单词的子字符串的查询片段(例如,本例中的"a"),不存在匹配项;在这种情况下,下限和上限将相等。
您可以尝试一种corasick算法。自动机实际上是一个trie,而失败函数是trie的广度优先遍历。
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从包含m行的文件中提取n行,必要时(惰性地)重复该文件
- 编译包含字符串的代码时遇到问题
- c++库的公共头文件中应该包含什么
- 将包含C样式数组的对象初始化为成员变量(C++)
- 是否需要删除包含对象的"pair"?
- 为什么在这个代码结束循环中没有得到结束
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 如何将包含epoch时间的十六进制字符串转换为time_t
- 使用mongocxx驱动程序时包含头文件问题
- 如何在h文件中包含.o对象文件
- 在混合代码库中将C转换为C++时出现许多包含错误
- 试图对缓存进行跨步测试,但程序并没有结束
- VS2017,C++包含目录与附加包含目录,子文件夹包含失败-但为什么
- cmath抛出错误C2062、C2059、C2143和C2447.cmath包含在矢量文件中
- 为什么您需要C++头文件的包含保护
- 当调用switch语句中的函数时(即使函数不包含循环),似乎是永不结束的循环的问题
- char数组在结束前包含空字符
- 在单词词典中提取以片段开头/包含/结束的单词
- #在include保护结束之前将.cpp包含在.h文件中