2011-01-08 2 views
0

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

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

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 чеки, и моя голова только что повернулась, чтобы попытаться придумать вышеупомянутое решение.

ответ

6

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

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

Опция, которая приходит на ум сначала, состоит в том, чтобы выполнить частичную перетасовку. Есть множество примеров переполнения стека modified Fisher-Yates 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, который вам может пригодиться.

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

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

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

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

чувство!
Смежные вопросы