【问题标题】:Is it considered bad form to convert between collection types?在集合类型之间转换是否被认为是不好的形式?
【发布时间】:2012-10-22 17:00:48
【问题描述】:

我希望这个问题足够具体,可以被认为适合 StackOverflow。我检查了常见问题解答,我认为这是合格的,因为它是特定的并且与编程相关。

我正在用 Java 实现一个复杂的数据挖掘算法(FP-growth)。该算法的一些初始阶段要求我扫描一个大型数据库并保持对找到的每个项目类型的运行计数。这似乎非常适合Hashbag 接口。我在 Apache Commons 中找到了一个似乎对我有用。

所以现在,我的 HashBag 充满了 [itemType, count] 条目(对)。稍后在算法中,我需要对这些对执行大量类似列表的操作。在某些情况下,我必须按 itemType 对集合进行排序。在其他情况下,我必须按计数排序。这似乎非常适合List 接口。

我的结论是我必须将我的 Hasbag 转换为列表。然而不知何故,它感觉很脏,就像是在浪费空间和时间。有没有更聪明的方法来做到这一点,或者经常遇到编程问题,您必须在不同的时间以不同的方式处理您的集合,而转换是一种必要的邪恶?

另一种选择是制作我自己的界面,它确实是一个列表,但允许“袋式”添加。每次我想添加一些东西时,我都必须保持列表排序并使用自定义比较器执行二进制搜索。构建该集合可能比构建一个 Hashbag 需要更长的时间,但我会在最后节省转换步骤。关于哪个更可取的想法?

谢谢!

【问题讨论】:

  • 回想一下,对集合进行排序已经是一个 O(n log(n)) 操作。复制和排序会使 O(n + n log(n)) = O(n (1 + log(n)) - 不是微不足道的增加,但也不是一个戏剧性的增加。放不同的是,排序已经将集合的每个元素移动到几个不同的内存位置。如果排序本身不会,再次移动(复制)它们可能不会降低性能。

标签: java list collections bag


【解决方案1】:

回答我自己的问题!

我对上面 Louis Wasserman 提到的 Guava 库提供的不同类型的 Multiset 进行了一些试验。在我的特定测试用例中,我正在解析一个 1GB 的 XML 文件(书籍和作者的数据库)并创建一个非常大的 Multiset(记录每个作者在数据库中出现的次数)。完成解析后,我需要获得一个新的 Multiset,其中仅包含出现次数超过 x 次的作者,其中 x 是某个阈值。我还希望我的最终集按作者姓名排序。

这是我尝试过的两种不同方式(以及其他方式):

1) 在TreeMultiset 中收集原始计数,然后删除任何不符合阈值的计数 2) 在HashMultiset 中收集原始计数,然后创建一个新的TreeMultiset,在其中添加哈希集中的每个项目,计数满足阈值

事实证明,第二种方式明显更快(大约 25%),尽管有转换和额外的内存使用。显然,其中很大一部分是从二叉树中删除效率非常低。

所以这里的明确结论是,在这种情况下,转换是一个很好的举措(除非你有不允许它的内存限制)。

再次感谢你把我带到 Guava 图书馆,Louis!

【讨论】:

    【解决方案2】:

    但不知何故感觉很脏,就像浪费空间和时间。有没有更聪明的方法来做到这一点,或者经常遇到编程问题,您必须在不同的时间以不同的方式对待您的集合,而转换是一种必要的邪恶?

    有时需要在集合类型之间进行转换。如果有必要,“肮脏”或“不雅”或“愚蠢”并不真正相关。

    提前考虑这些事情也可能是错误的。实际的计算权衡通常很难掌握。例如,如果您将 HashBag 更改为 TreeBag,则插入会从 O(1) 变为 O(logN),但您可以避免排序和复制的开销。 “大哦”分析/思考不会给你一个明确的答案。事实上,真正的性能将取决于缩放因子、N 的值、包中的命中率和未命中率等等。

    我建议尝试以明显的方式实现事物,看看它是否表现得足够好......如果不是,则分析它以查看数据结构是否是主要瓶颈。然后根据对输入数据集的分析、和其他测量,找出从您的基线实现中提高性能的最佳方法。

    【讨论】:

    • 对瘫痪分析的好呼吁。感谢您的输入,您所说的所有其他内容都是有道理的,并且证实了我的怀疑,但很高兴从更有经验的程序员那里听到它。 :-)
    【解决方案3】:

    如果您使用 Guava's Multiset 而不是 Apache 的 Bag - 大致类似,但风格不同 - 您可以在不转换的情况下完成大部分操作。 Multiset.entrySet() 返回一个 Set<Entry<E>>Entry<E> 有效地表示一对元素和一个计数——这听起来可能是满足您对元素计数对进行操作的最佳方式,也许吧?您可以像迭代 Map.entrySet() 一样对其进行迭代。

    您可以使用Multisets.copyHighestCountFirst(Multiset) 来获得以最高频率优先顺序重新排序的多重集,并使用TreeMultiset 直接按元素排序。

    (披露:我为 Guava 做出了贡献。)

    【讨论】:

    • 我在等你的答案 =)
    • 哇,我完全不知道 Guava 项目。关于 Apache Commons 的一些事情让我很恼火,而且我是 Google 的忠实粉丝,所以我认为我对 Guava 很感兴趣。听起来 Multiset 确实应该很适合我。感谢您的提醒! :-)
    • Louis,请参阅下面的回复(作为对我自己帖子的回答),了解我在这里使用 Guava 解决问题的经验。效果很好!谢谢。
    【解决方案4】:

    我假设您使用的是 Apache Commons Collections HashBag 类。您是否考虑过改用TreeBag?它实现了相同的 Bag 接口,但根据您提供的比较器有效地保持数据排序。

    也就是说,当您需要更改排序顺序时,通常没有比将集合复制到具有不同比较器的新集合更好的解决方案了。

    【讨论】:

    • 是的,这听起来确实是另一个有效的选择。感谢您的意见。
    猜你喜欢
    • 1970-01-01
    • 2011-06-08
    • 2017-12-18
    • 2021-12-18
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    • 2023-03-29
    相关资源
    最近更新 更多