快速从C#HashSet中获取随机元素

时间:2015-05-27 12:45:59

标签: c# performance random hashset

我需要存储一组元素。我需要的是

的功能
  1. 删除(单个)元素和
  2. 添加(组)元素和
  3. 每个对象应仅在集合中
  4. 从集合中获取随机元素
  5. 我选择了HashSet(C#),因为它运行快速方法来删除元素( hashSet.remove(element)),添加集合( hashSet.UnionWith (anotherHashSet))和HashSet的性质保证没有重复,因此需要处理需求1到3。

    我发现获取随机元素的唯一方法是

    Object object = hashSet.ElementAt(rnd.Next(hashSet.Count));
    

    但这非常慢,因为我为地图的每个像素调用一次(从多个起点创建随机洪水填充;此刻mapize 500x500但我想变大)并且hashset相当很多东西。 (一项快速测试表明,在再次收缩之前,它会爆发5752个条目。)

    性能分析(CPU采样)告诉我,我的ElementAt调用占用了50%以上。

    我意识到500x500对大型hashset的操作并不容易,但其他操作(Remove和UnionWith)和ElementAt一样被调用,所以主要问题似乎是操作而不是调用次数。

    我模糊地理解为什么从HashSet中获取某个元素非常昂贵(与从列表或其他有序数据结构中获取它相比,但我只是想随机选择。真的可以这么难有没有办法绕过它?我的目的是否有更好的数据结构?

    将所有内容更改为列表并没有帮助,因为现在其他方法成为瓶颈,需要更长的时间。

    将HashSet转换为数组并从那里选择我的随机元素预计无济于事,因为从数组中选择一个随机元素很快,首先将hashset转换为数组需要比运行hashSet.ElementAt更长的时间本身。

    如果您想更好地了解我要做的事情:A link to my question and the answer.

2 个答案:

答案 0 :(得分:6)

基本问题是索引。

在数组或列表中,数据由其coördinate索引 - 通常只是一个简单的int索引。在HashSet中,您自己选择索引 - 关键。然而,副作用是没有“coördinate” - 问题“索引3处的元素”真的没有意义。它实际实现的方式是枚举整个HashSet,逐项后面,并返回第n个项目。这意味着要获得第1000个项目,您必须在此之前枚举所有999个项目。这伤害了。

解决此问题的最佳方法是根据HashSet的实际密钥选择随机数。当然,这只有在选择随机密钥时才有效。

如果您无法以令人满意的方式随机选择密钥,则可能需要保留两个单独的列表 - 每当您向HashSet添加新项目时,将其密钥添加到{{ 1}};然后,您可以轻松地从List<TKey>中选择一个随机密钥,然后按照它进行操作。根据您的要求,重复可能不是什么大问题。

当然,如果您只进行一次枚举,则可以保存List枚举 - 例如,在搜索ElementAt之前,您可以将其转换为HashSet。这只有在您一次选择多个随机索引时才有意义(例如,如果您一次随机选择5个索引,您将平均保存 1/5的时间) - 如果你总是选择一个,然后修改List并选择另一个,那就没有用了。

根据您的确切用例,可能值得查看HashSet。它的工作方式与SortedSet类似,但它维护键中的顺序。有用的部分是你可以使用HashSet方法来获得一系列的键 - 如果键很稀疏,但在任意范围之间很好地平衡,你可以非常有效地使用它。您只需先随机选择一个范围,然后使用GetViewBetween获取范围内的项目,并从中选择一个随机的项目。实际上,这将允许您对搜索结果进行分区,并且应该节省相当多的时间。

答案 1 :(得分:5)

我认为OrderedDictionary可能适合您的目的:

var dict = new OrderedDictionary();

dict.Add("My String Key", "My String");
dict.Add(12345, 54321);

Console.WriteLine(dict[0]); // Prints "My String"
Console.WriteLine(dict[1]); // Prints 54321

Console.WriteLine(dict["My String Key"]); // Prints "My String"
Console.WriteLine(dict[(object)12345]);   // Prints 54321 (note the need to cast!)

这有快速添加和删除,以及O(1)索引。它仅适用于object键和值 - 没有通用版本。