Есть ли более эффективный способ рандомизировать набор результатов LINQ? - PullRequest
0 голосов
/ 08 января 2011

Я создал функцию для возврата случайного набора представлений в зависимости от объема, переданного ей, но я беспокоюсь, что, хотя теперь он работает с небольшим объемом данных, когда проходит большой объем, он будет стать эффективными и вызвать проблемы.

Есть ли более эффективный способ сделать следующее?

    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;
    }

Как я уже сказал, у меня это полностью работает и возвращает желаемый результат. Просто мне там не нравятся чеки foreach и while, и моя голова только что превратилась в кашу, теперь пытающуюся найти вышеупомянутое решение.

1 Ответ

6 голосов
/ 08 января 2011

(Пожалуйста, прочитайте все до конца, поскольку есть различные аспекты эффективности, которые следует учитывать.)

Существуют, безусловно, более простые способы сделать это - и, в частности, вам действительно не нужно выполнятьзапрашивать правильные ответы повторно.Почему вы извлекаете randSubmissions внутри цикла?Вам также следует взглянуть на ElementAt, чтобы избежать Skip и FirstOrDefault - и иметь в виду, что, поскольку randSubmissions является списком, вы можете использовать обычные операции со списками, такие как свойство Count и индексатор!

Первый вариант, который приходит на ум, - это частичное перемешивание.Существует множество примеров переполнения стека модифицированного шагала Фишера-Йейтса .Вы можете очень легко изменить этот код, чтобы избежать перемешивания всего списка - просто перемешивайте его, пока не получите столько случайных элементов, сколько вам нужно.Фактически, в наши дни я бы, вероятно, реализовал этот случай случайным образом немного иначе, чем вы могли бы просто вызвать:

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 в вашем методе.У меня есть статья о предпочтительных способах использования Random, которую вы можете найти полезной.

Одна из альтернатив, которую вы можете рассмотреть, - это вычисление count изисправьте записи, не извлекая их все, затем отработайте выигрышные записи, рассчитав случайный выбор «идентификаторов строк», а затем повторно используйте ElementAt (с единообразным порядком).В качестве альтернативы, вместо того, чтобы извлекать полные представления, просто извлекайте их идентификаторы.Перемешайте идентификаторы, чтобы выбрать n случайных (которые вы вводите в List<T>, затем используйте что-то вроде:

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

Я полагаю, что в SQL будет использоваться предложение «IN», хотя существуют ограниченияотносительно того, сколько записей можно извлечь следующим образом.

Таким образом, даже если у вас 100 000 правильных записей и 3 победителя, вы получите только 100 000 идентификаторов и 3 полные записи. Надеюсь, это имеет смысл!

...