【问题标题】:Faster way to count number of sets an item appears in?更快的方法来计算一个项目出现的集合数?
【发布时间】:2012-08-08 20:36:02
【问题描述】:

我有一个书签列表。每个书签都有一个关键字列表(存储为 HashSet)。我还有一组所有可能的关键字(“宇宙”)。

我想找到出现在书签中最多的关键字。

我有 1356 个书签,总共有 698,539 个关键字,有 187,358 个唯一关键字。

如果我遍历宇宙中的每个关键字并计算它出现在书签中的数量,我将进行 254,057,448 次检查。这在我的机器上需要 35 秒。

算法很简单:

var biggest = universe.MaxBy(kw => bookmarks.Count(bm => bm.Keywords.Contains(kw)));

使用Jon Skeet's MaxBy

我不确定是否可以加快速度,但有什么我可以做的吗?也许以某种方式并行化它?


dtb 的解决方案需要不到 200 毫秒的时间来构建宇宙并找到最大的元素。就这么简单。

var freq = new FreqDict();
foreach(var bm in bookmarks) {
    freq.Add(bm.Keywords);
}
var biggest2 = freq.MaxBy(kvp => kvp.Value);

FreqDict 只是我在Dictionary<string,int> 之上构建的一个小类。

【问题讨论】:

  • 在等待 35 秒过去时,它会给 CPU 带来多大压力?
  • @IneedHelp:查看任务管理器 (Win7) 中的性能选项卡,CPU 使用率从 1% 跃升至 25% 左右,然后稳定在 12% 左右。我有 4 个内核,超线程到 8 个。
  • @Mark:你有没有考虑在创建全域的时候统计每个关键词出现的次数?
  • @dtb:不……我没想到。创建宇宙只需要100ms;它只是在做一堆UnionWiths。如果我改用Dictionary<string,int> 并进行一些计数,我想它不会慢很多,它会给我更多信息。好奇的。不管怎样,我想递归地应用这个,用一个“缩小的宇宙”——也就是说,我正在为集合覆盖问题实施一个贪婪的解决方案。我想我仍然可以应用此解决方案,但我不会第二次添加任何新条目。明天早上试试。谢谢!
  • @Mark 除了计数之外,对于并行化,您始终可以尝试对来自PLINQuniverse.AsParallel()... 进行基准测试。请注意,它没有必须更快...

标签: c# algorithm optimization hashset


【解决方案1】:

您可以获取所有关键字,将它们分组,并获得最大的组。这会使用更多内存,但应该更快。

我试过这个,在我的测试中它快了大约 80 倍:

string biggest =
  bookmarks
  .SelectMany(m => m.Keywords)
  .GroupBy(k => k)
  .OrderByDescending(g => g.Count())
  .First()
  .Key;

试运行:

1536 bookmarks
153600 keywords
74245 unique keywords

Original:
12098 ms.
biggest = "18541"

New:
148 ms.
biggest = "18541"

【讨论】:

  • 这很漂亮。分组后尝试.MaxBy(g => g.Count()),而不是使用排序。
  • @erisco:谢谢。使用MaxBy 应该比排序快,但是我在测试中看不出区别。我猜这部分操作只是整个操作的一小部分,因此并没有什么实际区别。
  • 对于测试时间,可能你应该多次运行它们,例如 1000 次,因为初始化会花费太多时间。此外,我很确定您使用 for 循环的方法比 linq 运行得更快。
  • @SaeedAmiri:将项目循环到字典中与 GroupBy 所做的基本相同。使用 Linq 时总会有一些开销,因此您总是可以通过自己重写它来稍微加快速度,但另一方面,您可以通过使用框架中的方法来降低出现错误的风险。
  • 是的,我们可以避免开销并提高性能并增加风险并降低代码的可读性和维护性,但这完全取决于项目,如果 OP 高度关注性能,我建议这样做。
【解决方案2】:

我没有你的样本数据,也没有做过任何基准测试,但我会试一试。可以改进的一个问题是大多数bm.Keywords.Contains(kw) 检查都未命中,我认为这些是可以避免的。最受限制的是任何给定书签所具有的关键字集(即:它通常会比 Universe 小得多),因此我们应该从那个方向开始,而不是从其他方向开始。

我正在考虑这些方面的事情。内存要求要高得多,而且由于我没有对任何东西进行基准测试,它可能会更慢,或者没有帮助,但如果它不适合你,我会删除我的答案。

Dictionary<string, int> keywordCounts = new Dictionary<string, int>(universe.Length);
foreach (var keyword in universe)
{
    keywordCounts.Add(keyword, 0);
}

foreach (var bookmark in bookmarks)
{
    foreach (var keyword in bookmark.Keywords)
    {
        keywordCounts[keyword] += 1;
    }
}

var mostCommonKeyword = keywordCounts.MaxBy(x => x.Value).Key;

【讨论】:

  • 接受这个似乎是最快的解决方案,尽管它们都非常相似。
【解决方案3】:

您不需要遍历整个宇宙。想法是创建一个查找和跟踪最大值。

    public Keyword GetMaxKeyword(IEnumerable<Bookmark> bookmarks)
    {
        int max = 0;
        Keyword maxkw = null;

        Dictionary<Keyword, int> lookup = new Dictionary<Keyword, int>();

        foreach (var item in bookmarks)
        {
            foreach (var kw in item.Keywords)
            {
                int val = 1;

                if (lookup.ContainsKey(kw))
                {
                    val = ++lookup[kw];
                }
                else
                {
                    lookup.Add(kw, 1);
                }

                if (max < val)
                {
                    max = val;
                    maxkw = kw;
                }
            }
        }

        return maxkw;
    }

【讨论】:

  • 聪明。我正在使用字典,但我不认为同时存储最大值。让我看看节省了多少时间...
  • 不。不节省任何时间。甚至可能花费几毫秒。我认为每个添加的额外操作超过了进行 1 次迭代以在最后找到最大值。
【解决方案4】:

python 中的 50 毫秒:

>>> import random

>>> universe = set()
>>> bookmarks = []
>>> for i in range(1356):
...     bookmark = []
...     for j in range(698539//1356):
...         key_word = random.randint(1000, 1000000000)
...         universe.add(key_word)
...         bookmark.append(key_word)
...     bookmarks.append(bookmark)
...
>>> key_word_count = {}
>>> for bookmark in bookmarks:
...     for key_word in bookmark:
...         key_word_count[key_word] = key_word_count.get(key_word, 0) + 1
...

>>> print max(key_word_count, key=key_word_count.__getitem__)
408530590

>>> print key_word_count[408530590]
3
>>>

【讨论】:

  • 我很好奇为什么这在 Python 中更快。我现在基本上在做同样的事情。也许是因为我的键是字符串?
  • TBH 50ms 根本不是很准确。这取决于计算机速度,是的,整数可能更快?不确定。
猜你喜欢
  • 1970-01-01
  • 2019-09-21
  • 1970-01-01
  • 2013-08-22
  • 1970-01-01
  • 2014-06-12
  • 1970-01-01
  • 2018-09-19
  • 2011-07-02
相关资源
最近更新 更多