是否有更有效的方法来随机化一组LINQ结果?

Mat*_*hry 0 c# linq random

我已经生成了一个函数来取回一组随机的提交,具体取决于传递给它的数量,但是我担心即使它在大量数据传递时现在只使用少量数据,它也会变得很有效率.引起问题.

有更好的方法来做以下事情吗?

    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;
    }
Run Code Online (Sandbox Code Playgroud)

正如我所说,我已经完全正常工作并带回了想要的结果.只是我不喜欢foreachwhile检查那里,我的头刚刚变成糊状,现在试图提出上述解决方案.

Jon*_*eet 6

(请仔细阅读,因为要考虑效率的不同方面.)

肯定有更简单的方法 - 特别是,您实际上不需要重复执行查询以获得正确的答案.你为什么要randSubmissions在循环内取物?您还应该考虑ElementAt避免SkipFirstOrDefault- 并且请记住,作为randSubmissions列表,您可以使用常规列表操作,如Count属性和索引器!

首先想到的选项是执行部分随机播放.在改进的Fisher-Yates shuffle的 Stack Overflow上有大量示例.您可以非常轻松地修改该代码,以避免混乱整个列表 - 只需将其随机播放,直到您拥有所需数量的随机元素为止.事实上,这些天我可能会稍微改变一下,你可以调用:

return correctSubmissions.Shuffle(random).Take(amount).ToList();
Run Code Online (Sandbox Code Playgroud)

例如:

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

鉴于上述方法,您的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();
}
Run Code Online (Sandbox Code Playgroud)

我建议不要Random在你的方法中创建一个新实例.我有一篇关于Random您可能觉得有用的首选使用方法文章.

您可能需要考虑的一个替代方案是计算正确条目的计数而不将其全部取出,然后通过计算随机选择的"行ID"然后ElementAt重复使用(具有一致的顺序)来计算获胜条目.或者,不要拉动完整的提交,而只需提取他们的ID.随机抽取ID以随机选择(你放入一个List<T>,然后使用类似的东西:

return DbContext().Submissions
                  .Where(s => winningIds.Contains(s.Id))
                  .ToList();
Run Code Online (Sandbox Code Playgroud)

我相信这将在SQL中使用"IN"子句,尽管可以像这样检索多少条目存在限制.

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