在单词词典中提取以片段开头/包含/结束的单词

Fetch Words Starting/Containing/Ending With Fragment in a Word Dictionary

本文关键字:包含 结束 单词 开头 片段 单词词 提取      更新时间:2023-10-16

假设我们有一个来自英语词典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]中的所有后缀都必须以查询片段开头,因此这些后缀最初来自的所有单词都包含查询片段

现在,很明显,实际上拼写出所有的后缀需要大量的内存,但你不需要。后缀可以被认为只是一个单词的索引,也就是后缀开始的偏移量。因此,每个可能的后缀只需要一对整数:一个用于(原始)单词索引,一个用于该单词中后缀的索引。(你可以根据字典的大小巧妙地将这两个组合在一起,以节省更多的空间。)

总之,你所需要做的就是:

  1. 为所有单词生成一个包含所有单词后缀索引对的数组
  2. 根据它们的语义将它们作为后缀(而不是数值)进行排序。我建议std::stable_sort带有自定义比较器。这是最长的一步,但可以离线完成一次,因为您的字典是静态的
  3. 对于给定的查询片段,在排序后的后缀索引中找到下限和上限。该范围中的每个后缀都对应于一个匹配的子字符串(查询长度,从单词索引处单词的后缀索引开始)。请注意,有些单词可能匹配不止一次,而且匹配甚至可能重叠

为了澄清,这里有一个由单词"臭鼬"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的广度优先遍历。