【问题标题】:Way to store a large dictionary with low memory footprint + fast lookups (on Android)存储具有低内存占用 + 快速查找的大型字典的方法(在 Android 上)
【发布时间】:2010-02-16 21:52:34
【问题描述】:

我正在开发一个安卓文字游戏应用程序,该应用程序需要一个大型(约 250,000 个单词字典)可用。我需要:

  • 合理快速的查找,例如固定时间更可取,有时可能需要每秒进行 200 次查找来解决单词难题,并且可能需要在 0.2 秒内更频繁地进行 20 次查找来检查用户刚刚拼写的单词。

编辑:查找通常会询问“在字典中吗?”。我也想在单词中支持最多两个通配符,但这很容易,只需生成通配符可能是的所有可能字母并检查生成的单词(即 26 * 26 查找带有两个通配符的单词) .

  • 因为它是一个移动应用程序,所以使用尽可能少的内存并且只需要对字典数据进行少量初始下载是重中之重。

我第一次天真的尝试使用了 Java 的 HashMap 类,这导致了内存不足的异常。我已经研究过使用 android 上可用的 SQL lite 数据库,但这似乎有点矫枉过正。

什么是做我需要的好方法?

【问题讨论】:

  • 不能用安卓自带的字典吗?
  • 我想过这个,但你真的需要使用例如官方的拼字游戏词典或单词游戏疯了。

标签: java android algorithm data-structures complexity-theory


【解决方案1】:

您也可以通过更简单的方法来实现您的目标...如果这是一个文字游戏,那么我怀疑您正在处理 27 个字母的字母表。所以假设一个不超过 32 个字母的字母表,即每个字母 5 位。您可以使用 5 位/字母的普通编码将 12 个字母(12 x 5 = 60 位)塞进单个 Java long

这意味着实际上,如果您的单词长度不超过 12 个字母/单词,您可以将您的字典表示为一组 Java longs。如果您有 250,000 字,则将此集合简单地表示为单个、排序的 long 数组应该占用 250,000 字 x 8 字节/字 = 2,000,000 ~ 2MB 内存。然后通过二分查找进行查找,考虑到数据集的小规模,这应该非常快(少于 20 次比较,因为 2^20 会将您带到一百万以上)。

如果您的单词长度超过 12 个字母,那么 I 会将 >12 个字母的单词存储在另一个数组中,其中 1 个单词会以明显的方式由 2 个连接的 Java long 表示。

注意:这种方法之所以有效并且可能比 trie 更节省空间并且至少实现起来非常简单,是因为字典是恒定的......如果您需要修改数据集,搜索树是很好的,但是如果数据集是常量,通常可以通过简单的二分查找来运行。

【讨论】:

  • 哇,好主意。我会调查一下。我想知道这与例如之间的内存差异是什么。试一试。我发现了许多可以使用的免费且经过良好测试的 trie 实现,但我想这个想法似乎更专业。我想我需要做的就是编写“将字符串转换为长”函数并使用一个基本的集合类,它不应该太容易出错。要存储字典,我需要做的就是存储 longs 文件,然后在加载时将它们插入到搜索树中。我可以保存并加载实际的树,但文件会更大。
  • 您不需要在搜索树中插入任何内容...您只需将数组加载到内存中,然后在数组中进行二进制搜索。没有树。只有一个阵列。没有收藏类。除了多头数组之外没有任何东西!!!
  • 顺便说一句,如果您使用尝试,您最终会分配大量堆对象,这会加重 Java 垃圾收集器的负担。单个 long 数组对于垃圾收集器来说只有 1 个对象,并且它不包含指针,因此对收集器的压力是最小的。
  • @BobbyJim,您需要对字典代码进行单元测试。尝试为此使用测试驱动设计 - 只需编写单元测试 first 并在每次测试之后编写足够的库代码以使测试通过 - 你很可能会发现当你完成后你不需要一个库作为代码将足够简单,不需要一个。
  • @BobbyJim:JDK 有 java.util.Arrays.sort() 和 java.util.Arrays.binarySearch() 方法会很有帮助。
【解决方案2】:

我假设你想检查给定的单词是否属于字典。

看看bloom filter

布隆过滤器可以执行“X 是否属于预定义集合”类型的查询,且存储需求非常小。如果查询的答案是肯定的,那么它出错的概率很小(并且可以调整),如果查询的答案是否定的,那么答案就保证是正确的。

根据 Wikipedia 文章,您的 250 000 个单词的字典可能需要不到 4 MB 的空间,错误概率为 1%。

如果单词实际上包含在字典中,布隆过滤器将正确回答“在字典中”。如果字典中没有这个词,布隆过滤器可能会错误地给出“在字典中”的答案。

【讨论】:

  • 看起来很有趣。虽然这是一个文字游戏,所以我需要 100% 肯定的答案来判断这个词是否在字典中或用户会生气!
  • 布隆过滤器可能会给出误报。如果您的游戏有时会接受字典中没有的单词,这是否有问题?
  • 啊,它是“误报是可能的,但误报是不可能的”。我想 95% 的时间,用户会提交实际的单词。这取决于假词通过的频率,因为您不想将其作为游戏策略的一部分(这可能是一个有趣的游戏,尽管您试图通过看起来像真实词的非词来偷偷摸摸)!
  • 您可以通过改变布隆过滤器位数组的大小来调整误报率。较大的位数组具有较小的假阴性概率。
  • 我正在阅读更多内容,误报概率在 1 000 000 分之一的数量级,对于典型的布隆过滤器通常更高。换句话说,极不可能成为问题。谢谢。
【解决方案3】:

存储目录的一种非常有效的方法是Directed Acyclic Word Graph (DAWG)。

这里有一些链接:

【讨论】:

    【解决方案4】:

    你会想要某种trie。我认为也许ternary search trie 会很好。它们提供非常快速的查找和低内存使用。 This paper 提供了有关 TST 的更多信息。它还谈到了排序,因此并非所有内容都适用。 This article 可能更适用一些。正如文章所说,TSTs

    结合数字化的时间效率 尝试空间效率 二叉搜索树。

    正如this 表所示,查找时间与使用哈希表非常相似。

    【讨论】:

    • 我的原始帖子有一条评论说尝试占用大量内存,但我删除了它,因为我不太确定。阅读您的链接后,我仍然不确定。来自维基:“特里树以牺牲大小为代价优化速度。”和“在现代实践中,三叉搜索树通常被拒绝使用哈希表。此外,还有其他方法可以构造一个 trie。例如,一个基数树 trie,其中每个节点存储一个位字符串,并有两个子节点用于可能的下一位,可能比三元搜索树更紧凑而不会很慢。”
    • 我发现了一个用于 Java 的有趣的 apache 许可证 trie 实现:code.google.com/p/patricia-trie/wiki/Examples
    • 是的,我不确定它们在空间方面是否一定会更好,但是有一些方法可以减少这种情况,例如折叠只有一个后代的节点。您可能还想查看 HAT-tries
    • 我所知道的大多数 trie 实现都会为您完成崩溃。我只需要对一些我想的基准进行基准测试。谢谢,将研究 HAT 树。
    【解决方案5】:

    您也可以使用Android NDK 并在 C 或 C++ 中构建结构。

    【讨论】:

    • 这能让我做 Java 做不到的事情?
    • 此语句的任何来源或关于如何使用 c/c++ 使其更快的示例(包括 JNI 调用开销)?
    【解决方案6】:

    我使用的设备基本上都是从二进制压缩文件中工作的,其拓扑结构类似于二叉树的结构。在叶子处,您将拥有 Huffmann 压缩文本。查找节点将涉及必须跳到文件的各个位置,然后只加载真正需要的数据部分。

    【讨论】:

      【解决方案7】:

      “Antti Huima”建议的非常酷的想法试图存储字典单词using long。然后使用二分查找。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-28
        • 2011-01-31
        • 1970-01-01
        • 2013-09-07
        • 1970-01-01
        相关资源
        最近更新 更多