简体   繁体   中英

Selecting multiple objects from a list using LINQ

I need to select random objects from a list of horses. At the moment I'm trying to use LINQ. What I need to have happen is the user selects how many object they want and that number of object is randomly selected from the list.

Here is my code at the moment:

Random rand = new Random();

//creates a list of horses that haven't yet been assigned to players
List<Horse> unassignedHorses = retrieveUnassignedHorses(); 

List<Horse> selectedHorses = unassignedHorses.OrderBy(m => rand.Next())
    .Take(numberHorses)
    .ToList();

My question is this a good way of doing this or is an even better way.

There are two general approaches that you can use when you want to get "N random items from a collection".

  1. Choose a random item, check if it hasn't been chosen before, if it has been, choose a new one.
  2. Shuffle the entire collection, then grab the first N items.

You choose the second option (although you used an inefficient and biased method of doing so).

Which is best, well, that depends. First off, technically, the first option is O(infinity) because there is the possibility of randomly selecting the same item over and over and over again. If you want to speak of average cases, it becomes a valid option to consider.

The first option is best when the number of items you want is a small percentage of the total size of the collection. If you only want to grab say 1% of the collection's items then the odds of randomly choosing already chosen items is very, very small.

The second option, shuffling, on the other hand, is better if you want a large percentage of the items in the collection, because you do the same amount of work regardless of whether you want 1 item or all of them. If you only want 1% of the items in the collection then you end up wasting a whole bunch of effort shuffling data that you just don't care about at all.

Now, what we'd really want to do is randomly shuffle the first N items of the collection, but without shuffling all of the items that are left over.

Interestingly enough, this is actually very easy to do. A much more preferable method of shuffling a list is to count down from the highest index to the lowest, pick an item below the current index, and swap it with the current index. This doesn't introduce a bias, unlike sorting, it's O(n) unlike sorting which is O(n*log(n)), and better still, it's super easy to stop part way . So if we implement this shuffling algorithm:

public static IEnumerable<T> Shuffle<T>(
    this IEnumerable<T> source, Random random)
{
    var list = source.ToList();
    for (int i = list.Count; i > 0; i--)
    {
        var index = random.Next(i);
        var temp = list[index];
        list[index] = list[i - 1];
        list[i - 1] = temp;
        yield return temp;
    }
}

We can now write out your solution as:

var selectedHorses = unasignedHorses.Shuffle(rand).Take(numberHorses);
int numberHorses = 3; //or some user input
Random r = new Random();
List<Horse> unassignedHorses = retriveUnassignedHorses();
List<Horse> assignedHorses = new List<Horse>();
do
{
  int rIint = r.Next(0, unnasignedHorses.Count);
  if(!assignedHorses.Contains(unassignedHorses[rInt]))
  {
    assignedHorses.Add(unassignedHorses[rInt]);
  }
} (while assignedHorses.Count != numberHorses);

Like I said in the comments though, if your way works now and it makes sense to you, then why change it? I haven't tried this code but I would think something like this would accomplish what you're looking for. It may seem overly complex but that's how my solutions seems to start and then I end up streamlining them...

So you want to pick random object from list

I guess it can be something done like:

int r = rnd.Next(numberHorses.Count);
Horse  selectedHorse  = unnasignedHorses[r];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM