【问题标题】:Take 30 random elements from list从列表中取出 30 个随机元素
【发布时间】:2019-06-05 12:14:17
【问题描述】:

我有一个字符串列表,其中包含大量带有文本的主题标签。 ex #csharp #java 等。 每个主题标签都是列表中的一个自己的字符串。我现在想随机总是 console.writeline 该列表的 30 项。

列表


List<string> Hashtags = new List<string>();

写信


 foreach (var x in Hashtags) {

                Console.WriteLine(x);
            }

理想情况下,我永远不想在随机 30 中使用相同的标签。

【问题讨论】:

  • 到目前为止您尝试过什么?什么没用?
  • 30个字符串都以“#”开头?
  • 你在哪里尝试解决这个问题?这不是家庭作业写作服务。
  • @MischaMorf - Guid.NewGuid() 只保证是唯一的,而不是随机的。您应该始终使用 RNG,而不是 Guid 随机性。
  • @MischaMorf 不要在 cmets 中发布尝试。用这些更新问题。

标签: c# list random


【解决方案1】:

你应该试试这个:

var rnd = new Random();

foreach (var x in Hashtags.OrderBy(x => rnd.Next()).Take(30))
{
    Console.WriteLine(x);
}

这具有O(n^2) 的复杂性,但易于阅读。

如果您想提高效率,请尝试使用 Fisher-Yates Shuffle,它是 O(n),但可读性较差:

var take = 30;

var rnd = new Random();

for (var i = 0; i < (Hashtags.Count < take ? Hashtags.Count : take); i++)
{
    var j = rnd.Next(Hashtags.Count);
    (Hashtags[i], Hashtags[j]) = (Hashtags[j], Hashtags[i]);
}

foreach (var x in Hashtags.Take(take))
{
    Console.WriteLine(x);
}

但请记住,最好为每个线程实例化一次 Random,因此这是一种更好的方法:

[ThreadStatic]
private static Random rnd = new Random();

【讨论】:

  • 唯一的缺点是您需要订购完整的系列才能挑选30件物品。
  • @Magnus - 没错,但即使有 1_000_000 个元素的列表,在我的计算机上进行排序也需要不到半秒的时间。从好的方面来说,它可以保证完成,并且可以使用比请求数量更小的源列表。
  • @Enigmativity 不过,这是非常短但效率极低的解决方案。不仅您要对整个集合进行排序,这显然是一个问题,而且甚至更多 - 您根本不需要对其进行排序,您只需要对给定数量的项目进行洗牌。抱歉,这不应该是一个公认的答案。
  • @zhulien - 不,你是对的。我没有检查你的。你是对的。你的效率更高。
  • @zhulien - 我已经更新了我的。我觉得我现在处于同等水平。 :-)
【解决方案2】:

这应该可以。它是高效的,因为它只洗牌所需数量的项目,而不是整个集合。您将要从数组中获取的元素数量作为参数传递(elementCount)。

private static Random randomGenerator = new Random();

static void Main()
{
    var hashtags = new List<string>() { "c#", "javascript", "ef", "asp.net" };

    var result = GetRandomItems<string>(hashtags, 2);

    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}

private static IEnumerable<T> GetRandomItems<T>(IEnumerable<T> collection, int elementCount)
{
    var collectionCount = collection.Count();

    if (elementCount > collectionCount)
    {
        elementCount = collectionCount;
    }

    var collectionCopy = collection.ToList();

    var randomIndex = randomGenerator.Next(0, collectionCopy.Count);

    for (var index = 0; index < elementCount; index++)
    {
        var tempElement = collectionCopy[index];

        collectionCopy[index] = collectionCopy[randomIndex];
        collectionCopy[randomIndex] = tempElement;

        randomIndex = randomGenerator.Next(index + 1, collectionCopy.Count);
    }

    return collectionCopy.Take(elementCount);
}

【讨论】:

  • 不错。与其制作列表的副本,不如改组 List&lt;int&gt;(collection.Count())(表示索引)会更有效。
  • 它只是随机播放所需数量的项目,这很好。
  • (仅供参考,关于.Select 的评论是您根本不需要它;collection.ToList() 将返回collection 的副本)
【解决方案3】:

调用 30 次 Random.next

https://docs.microsoft.com/en-us/dotnet/api/system.random.next

var random = new Random();

//calls this n times in a loop:
Console.writeline(Hashtags[random.next(Hashtags.Count])

【讨论】:

  • 由于random.next 代码可以多次返回相同的值,因此没有任何逻辑可以停止两次获取相同的主题标签。
  • @MischaMorf - 如果你想避免重复,这不起作用。
  • @Enigmativity 我只是在写出字符串后从字符串列表中删除字符串。然后就可以了:)
  • @MischaMorf - 像这样调整数组的大小相当低效。
【解决方案4】:

自己搞定了,有点冗长,但希望更容易理解。

 var random = new Random();
 var uniques = Hashtags; 

 for (var i = 0; i < 30; i++) {  
     var index =  random.Next(0, uniques.Count());
     Console.WriteLine(uniques[index]);
     uniques.RemoveAt(index); 
 }

【讨论】:

  • 道歉 - 你在我最终编辑之前发布了这个。显然你必须在顶部添加一些 using 语句,但我认为 IDE 至少会给你。
  • 它仍然无法编译,即使您进行了最终编辑。
  • 抱歉,我以为我发布了最终编辑,但先关闭了标签 :)
  • 另外,没有方法DateTime.UtcNow.ToUnixTimeSeconds() - 它应该是DateTimeOffset.UtcNow.ToUnixTimeSeconds()。现在,即使您尝试调用new Random(DateTimeOffset.UtcNow.ToUnixTimeSeconds()),这也是一个语法错误。即使它确实有效,这样调用它也比仅仅调用new Random()(内部使用Environment.TickCount)糟糕一千倍。
  • 对于多次执行,应该使用Randomsame 实例,而不是尝试创建一个新实例或更糟,使用秒作为种子。使用秒意味着在一整秒内将产生相同的数字序列。
猜你喜欢
  • 2015-09-22
  • 2018-10-19
  • 2014-02-07
  • 2018-02-12
  • 1970-01-01
  • 2023-01-11
  • 2013-01-13
  • 2013-12-17
  • 1970-01-01
相关资源
最近更新 更多