从哈希集合中获取随机元素?

11

我正在使用下面这段代码将我的文本文件加载到一个哈希集合中。

HashSet<string> hashs = new HashSet<string>(File.ReadLines("textFile.txt"));

请问有没有一种简单的方法可以从文本文件中获取随机一行呢?

假设textFile.txt包含10行,我想要随机选取其中一行。


1
你尝试过什么?你是否尝试使用System.Random类来生成0到<行数>之间的随机数,然后按索引引用该项?这些都是MSDN库中已经记录的任务。http://mattgemmell.com/2008/12/08/what-have-you-tried/ - David
6个回答

38

可以通过简单回答来避免每次枚举整个数组:

private static readonly Random     random  = new Random();
private static readonly HashSet<T> hashset = new HashSet<T>();

...

T element = hashset.ElementAt(random.Next(hashset.Count));

13
ElementAt仍然会枚举元素直到达到指定的索引,因此它不会非常快速。 - Zonko
1
如果在多线程环境中哈希集合元素发生更改,可能会返回InvalidOperationException(集合已修改;枚举操作可能无法执行)。可以通过在.ElementAt行上使用lock(object)或将hashset转换为.ToArray()并存储为新变量来避免此问题,然后在rand Count和.ElementAt中使用该变量,但这可能不是内存有效的。 - Ma Dude

17
Random randomizer = new Random();
string[] asArray = hashs.ToArray()
string randomLine = asArray[randomizer.Next(asArray.length)];

20
性能方面相当低效。虽然我不知道更好的方法,但只是说一下。 - batman

3

您可以生成0到集合大小之间的随机数,然后遍历该集合,直到达到其索引与生成的数字相同的项。然后将此项作为随机元素进行选择。


1
那个代码会是什么样子?我不确定该怎么写 :) - user1213488

1
如果您打算绘制多个随机值,则高效的方法是使用带有整数键的字典存储信息。
HashSet<string> hashs = new HashSet<string>();
Dictionary<int, string> lookup = new Dictionary<int, string>();
foreach (string line in File.ReadLines("textFile.txt")) {
    if (hashs.Add(line)) {
        lookup.Add(lookup.Count, line);
    }
}
        
int randomInt = new Random().Next(lookup.Count);
string randomLine = lookup[randomInt];

在这个例子中,你可以使用列表,但是使用字典也可以删除单个元素而不影响顺序。

1

或许更适用于任何可枚举的一般解决方案

public static class RandomExtensions
{
    private static readonly Random rnd = new Random();
    private static readonly object sync = new object();

    public static T RandomElement<T>(this IEnumerable<T> enumerable) {
        if (enumerable == null)
            throw new ArgumentNullException("enumerable");

        var count = enumerable.Count();

        var ndx = 0;
        lock (sync) 
            ndx = rnd.Next(count); // returns non-negative number less than max

        return enumerable.ElementAt(ndx); 
    }
}

ElementAt 会针对空集合抛出异常。 - Sergey Berezovskiy
3
如果 ElementAt 抛出异常,那么 RandomElement 也应该抛出相同的异常。在这种情况下,应该添加一个 RandomElementOrDefault 方法。 - Vasea

-1

自从 .Net Framework 3.5 版本以后,你可以使用 Linq 和它的 Enumerable.First() 扩展方法。 如果没有指定任何条件作为参数,该方法将返回

序列中的第一个元素。

请注意,使用 Enumerable.First() 需要你的 HashSet<> 至少包含一个元素。 要检查这个前提条件,你可以使用 HashSet<>.Count 或者通过 Linq 使用 Enumerable.Any() 再次不指定条件。

HashSet<T> hashSet = new HashSet<T>();

...

if(hashSet.Any())
{
  T randomElement = hashSet.First()
}

除了上述方法,您还可以使用 Enumerable.FirstOrDefault() 作为替代方案,以便在 HashSet<T> 为空时检索任何默认值。

T randomElement = hashSet.FirstOrDefault(default(T));

2
这将如何返回一个随机项? - derHugo
@derHugo HashSet是一种未排序的数据结构,因此首先会是随机的。 - orion_tvv
@orion_tvv 我猜每次调用它仍然会返回相同的元素... random != unsorted - derHugo
是的,如果您想通过多次调用获取不同的项目,则不应使用HashSet,并且您必须支付转换为类似数组的结构的费用。但对于大多数用例,第一个项目是很好的折衷方案。 - orion_tvv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接