【问题标题】:Is there a more efficent way to randomise a set of LINQ results?有没有更有效的方法来随机化一组 LINQ 结果?
【发布时间】:2011-01-08 10:20:08
【问题描述】:

我已经创建了一个函数来根据传递给它的数量取回一组随机提交,但我担心即使它现在在传递大量数据时处理少量数据,它也会变得高效并引起问题。

有没有更有效的方法来做以下事情?

    public List<Submission> GetRandomWinners(int id)
    {
        List<Submission> submissions = new List<Submission>();
        int amount = (DbContext().Competitions
                     .Where(s => s.CompetitionId == id).FirstOrDefault()).NumberWinners;

        for (int i = 1 ; i <= amount; i++)
        {
            bool added = false;
            while (!added)
            {
                bool found = false;

                var randSubmissions = DbContext().Submissions
                    .Where(s => s.CompetitionId == id && s.CorrectAnswer).ToList();

                int count = randSubmissions.Count();
                int index = new Random().Next(count);

                foreach (var sub in submissions)
                {
                    if (sub == randSubmissions.Skip(index).FirstOrDefault())
                        found = true;
                }

                if (!found)
                {
                    submissions.Add(randSubmissions.Skip(index).FirstOrDefault());
                    added = true;
                }
            }
        }
        return submissions;
    }

正如我所说,我已经完全正常工作并恢复了想要的结果。只是我不喜欢那里的foreachwhile 支票,我的头刚刚转向糊状,现在试图提出上述解决方案。

【问题讨论】:

    标签: c# linq random


    【解决方案1】:

    (请通读一遍,因为要考虑效率的不同方面。)

    肯定有更简单的方法可以做到这一点 - 特别是,您真的不需要重复执行查询以获得正确答案。为什么要在循环内获取randSubmissions?您还应该查看ElementAt 以避免SkipFirstOrDefault - 请记住,由于randSubmissions 是一个列表,您可以使用普通的列表操作,例如Count 属性和索引器!

    首先想到的选项是执行部分随机播放。在 modified Fisher-Yates shuffle 的 Stack Overflow 上有大量示例。您可以非常轻松地修改该代码以避免打乱整个列表 - 只需打乱它,直到您获得所需数量的随机元素。事实上,这些天我可能会以稍微不同的方式实现该 shuffle,您可以调用:

    return correctSubmissions.Shuffle(random).Take(amount).ToList();
    

    例如:

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
    {
        T[] elements = source.ToArray();
        for (int i = 0; i < elements.Length; i++)
        {
            // Find an item we haven't returned yet
            int swapIndex = i + rng.Next(elements.Length - i);
            T tmp = elements[i];
            yield return elements[swapIndex];
            elements[swapIndex] = tmp;
            // Note that we don't need to copy the value into elements[i],
            // as we'll never use that value again.
        }
    }
    

    鉴于上述方法,您的 GetRandomWinners 方法将如下所示:

    public List<Submission> GetRandomWinners(int competitionId, Random rng)
    {
        List<Submission> submissions = new List<Submission>();
        int winnerCount = DbContext().Competitions
                                     .Single(s => s.CompetitionId == competitionId)
                                     .NumberWinners;
    
        var correctEntries = DbContext().Submissions
                                        .Where(s => s.CompetitionId == id && 
                                                    s.CorrectAnswer)
                                        .ToList();
    
        return correctEntries.Shuffle(rng).Take(winnerCount).ToList();
    }
    

    我建议不要在您的方法中创建 Random 的新实例。我有一个article on preferred ways of using Random,您可能会觉得它很有用。

    您可能要考虑的一种替代方法是计算出正确条目的计数,而不是全部获取它们,然后通过计算“行 ID”的随机选择然后使用 @ 987654334@重复(顺序一致)。或者,不要提取完整的提交,而只提取他们的 ID。将 ID 随机排列以选择 n 个随机 ID(您将其放入 List&lt;T&gt;,然后使用类似:

    return DbContext().Submissions
                      .Where(s => winningIds.Contains(s.Id))
                      .ToList();
    

    我相信这将在 SQL 中使用“IN”子句,尽管这样可以检索多少条目是有限制的。

    这样即使您有 100,000 个正确条目和 3 个获胜者,您也只会获取 100,000 个 ID,但会获取 3 个完整记录。希望这是有道理的!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-29
      • 1970-01-01
      • 2019-10-25
      • 1970-01-01
      • 2011-05-02
      • 1970-01-01
      相关资源
      最近更新 更多