正如您已经指出的那样,后缀树或基数树可能是要走的路。我建议:
创建一个radix tree,将ID存储在叶子中。首先查看this answer 中的链接,但我相信您必须对找到的任何内容进行微调以满足您的需求;
-
创建一个将 id 映射到树中路径的字典。这将允许您通过 id 快速检索句子(找到路径,按照它安装句子)。请注意,这会使插入和删除有点昂贵:每次更改非叶节点时,每个后代都需要在 dict 中更新其路径;
2.1。另一种选择(以防路径结束太长)是让每个节点存储对其父节点的引用,因此 dict 只需要引用叶节点。我相信大多数实现都不会这样做,因为尝试的主要目标是加快查找速度,而不是压缩文本本身。
通配符搜索有点棘手,具体取决于您需要的复杂性。提供的示例很简单:跟随节点作为前缀,直到找到通配符,然后返回所有后代。在这种情况下,一般的 trie 可能比更专业的基数树更容易处理,但对空间的要求更高。
顺便说一句,您还可以优化您的基数树以占用更少的空间,通过使用一些间接在节点中插入字符串,并为长的公共子字符串添加额外的节点。示例:
unique_strings = [ # Not a real array, just an hypothetical "intern table"
"The quick ",
"brown fox ",
"green frog ",
"jumps over the lazy ",
"dog.",
"fox.",
"frog.",
]
radix_trie = (0, { # The quick *
"b":(1, { # The quick brown fox *
"j":(3, { # The quick brown fox jumps over the lazy *
"d":(4,{},1), # The quick brown fox jumps over the lazy dog.
"f":(6,{},3), # The quick brown fox jumps over the lazy frog.
}),
}),
"g":(2, { # The quick green frog *
"j":(3, { # The quick green frog jumps over the lazy *
"f":(5,{},2), # The quick green frog jumps over the lazy fox.
}),
}),
})
# The nodes ("b", "j") and ("g", "j") wouldn't occur in a regular radix tree,
# since they have no siblings. Adding them, however, gives a net gain of space.
#
# "jumps over the lazy " is a common substring of
# "brown fox jumps over the lazy " and
# "green frog jumps over the lazy fox."
# which would occur naturally in a radix tree with only the 3 sentences given.
paths = {
1:("b", "j", "d"),
2:("g", "j", "f"),
3:("b", "j", "f"),
}
当然,对于您的示例,这很容易设置,但是“在野外”找到重复的子字符串会有点棘手。 (在 any 对字符串中找到长的公共子字符串:非常昂贵的操作 可行,请参阅更新)但是,假设插入/删除是一种不常见的操作,那不应该是一个大问题。
注意:我建议使用基数树而不是 trie,因为前者的空间要求要小得多。
更新:以防万一您打算自己解决问题,这里还有一个使用基数树压缩数据的提示:根据维基百科在 longest common substring 上的文章,您可以构建一个generalised suffix tree 并使用它来查找两个或多个字符串的公共子字符串(它还提到它主要用于生物信息学)。为您的基数树的节点(或者,至少,超过一定大小的节点)创建一个,您可以找到将它们拆分为较小节点的情况。
使用您的示例,“常规”(没有独生子女)基数树将是:
radix_tree = ("The quick ", {
"b":("brown fox jumps over the lazy ", {
"d":("dog.",{},1),
"f":("frog.",{},3),
}),
"g":("green frog jumps over the lazy fox.", {}, 2),
})
这显然不能很好地压缩您的文本。但是在为每个节点中的单词集创建一个后缀树之后,很明显" jumps over the lazy " 是一个很好的候选对象,可以在两个或多个节点中进行实习和重用(导致在我之前展示的示例中)。节省的空间将始终为(string_length - (1..2)*sizeof_node) * num_nodes(前缀/后缀为1,其余为2),因此在进行此优化时根本不需要考虑短字符串。
很复杂,是的,正如 Adam Mihalcin 所指出的,纯 Python 解决方案的成本可能太高,无法存储非常大的数据集。但如果没有现成的解决方案,这是我首先尝试的......