【问题标题】:An efficiently stored dictionary. Does this data structure exist and what is it called?高效存储的字典。这种数据结构是否存在,它叫什么?
【发布时间】:2012-02-18 00:14:56
【问题描述】:

我想要一种数据结构,它可以存储大量彼此相似的低熵数据。我想有效地存储它们(以某种方式压缩)并通过索引或匹配检索。快速检索比压缩更重要,但是不压缩存储它们不是一种选择。

我能想到的最好的例子是存储从大量文本中提取的十亿个书面句子(在磁盘上以压缩形式)。

dict:
1: 'The quick brown fox jumps over the lazy dog.'
2: 'The quick green frog jumps over the lazy fox.'
3: 'The quick brown fox jumps over the lazy frog.'

如果两个句子相同,它们应该具有相同的索引。

我想通过索引或通配符匹配来检索它们(正则表达式也很好,但不是必需的)。即:

dict.get(1) => 'The quick brown fox jumps over the lazy dog.'
dict.match('The quick brown *') => [1, 3]

我可以压缩每个句子,但这忽略了许多条目相似的事实。

我可以对它们进行排序并存储差异。但这很难添加和删除元素。

应该支持unicode。

我确信有一些树结构可以做到这一点。

如果它有 python 包装器,则加分。

这个https://hkn.eecs.berkeley.edu/~dyoo/python/suffix_trees/ 看起来非常接近,但自 2002/py2.2 以来没有看到任何动作,我无法让它运行。如果有更新/更好的选择,我很想听听。

我将 bioinformatics 标签包括在内,因为我知道 suffix_trees 和类似的数据结构在那里使用。

【问题讨论】:

  • this answer 中的链接可能会有所帮助。
  • 在这种规模下,您需要仔细考虑您的要求。例如,当您说“索引匹配”时,您的意思是它们按照您需要保留的特定顺序,还是只是您需要按照固定顺序获取它们? (前者更难,因为相似的句子可能不会按顺序排列。)当你说“通配符匹配”时,你是指前缀还是任何地方的星号? (前者更容易,因为这样你就可以使用树形结构。)

标签: python data-structures bioinformatics


【解决方案1】:

正如您已经指出的那样,后缀树或基数树可能是要走的路。我建议:

  1. 创建一个radix tree,将ID存储在叶子中。首先查看this answer 中的链接,但我相信您必须对找到的任何内容进行微调以满足您的需求;

  2. 创建一个将 id 映射到树中路径的字典。这将允许您通过 id 快速检索句子(找到路径,按照它安装句子)。请注意,这会使插入和删除有点昂贵:每次更改非叶节点时,每个后代都需要在 dict 中更新其路径;

    2.1。另一种选择(以防路径结束太长)是让每个节点存储对其父节点的引用,因此 dict 只需要引用叶节点。我相信大多数实现都不会这样做,因为尝试的主要目标是加快查找速度,而不是压缩文本本身。

  3. 通配符搜索有点棘手,具体取决于您需要的复杂性。提供的示例很简单:跟随节点作为前缀,直到找到通配符,然后返回所有后代。在这种情况下,一般的 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 解决方案的成本可能太高,无法存储非常大的数据集。但如果没有现成的解决方案,这是我首先尝试的......

【讨论】:

  • 还要注意与真正的压缩方法的相似之处。您可能还想阅读霍夫曼编码。
【解决方案2】:

您的问题听起来与trie 的用例完全一样,它是一种基于树的数据结构,用于按前缀存储字符串。我自己没有使用过这些实现,但是在 Google 代码上的快速搜索发现开源 trie 项目 hereherehere。前两个是Java,第三个是C++。我希望为 Python 编写一个围绕 C++ 的包装器会比围绕 Java 编写一个包装器更容易,因为 Python 具有与 C 互操作性的内置功能。

编辑

我检查了 GitHub,并在 Python 实现方面取得了一些成功。我找到了 Python trie 实现 hereherehere

但是,如果您真的要处理 10 亿个句子,那么即使是编写得非常好的纯 Python 实现(这三个都是)也可能会耗尽内存。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-06
    • 2017-02-17
    • 1970-01-01
    • 1970-01-01
    • 2015-07-13
    • 2013-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多