David Thielen
David Thielen

Reputation: 32926

What's the best way to get a single random element from a List<>?

I need to get a random element from a list (that is not two of the values in the list). The following works fine:

Company dest = companies
    .Where(cpy => cpy != src && cpy != plyr.PowerUpInAction.Passenger.Destination)
    .OrderBy(pu => rand.Next())
    .ToList()[0];

Is there a better (ie more efficient) way to do this? Converting to a list strikes me as extra work.

thanks - dave

Upvotes: 6

Views: 98

Answers (4)

TrueEddie
TrueEddie

Reputation: 2233

You could do .First() instead of .ToList()[0]

Upvotes: 3

Wilson
Wilson

Reputation: 608

The accepted answer is perfectly fine BUT i would use .FirstOrDefault() instead of First().

The reason is .First() throws an exception (ArgumentNullException) if not row is found. This means your code will be stopped. You will have to add an extra try-catch to handle this.

.FirstOrDefault() in the same scenario will return a null object (in your case an empty Company object) that causes not damage at all. You won't have to add any try-catch neither nulls validations in your code.

Saving you to add more codes and future damages in case your company table in the future for some reason needs to be empty. This is a maintenance point of view.

Upvotes: 1

Kyle
Kyle

Reputation: 6684

Another possibility:

var items = companies.Where( cpy => cpy != src && cpy != plyr.PowerUpInAction.Passenger.Destination );
Company dest = items.First();
int min = int.MaxValue;
foreach( var company in items )
{
    int r = rand.Next();
    if( r < min )
    {
        min = r;
        dest = company;
    }
}

This avoids sorting the entire list as well as constructing a new list. It's still linear in the size of the list (since we have to evaluate what elements are valid).

Upvotes: 0

usr
usr

Reputation: 171178

Just retry until you have found an acceptable element. This is likely, because only two items are filtered out. In the best (likely) case we are done with just a single try. That's O(1).

int retryCount = 0;
while (retryCount < 5) {
 var index = GetRandomIndex(list.Count);
 if (list[index].IsNotFilteredOut)
  return list[index];
 else
  retryCount++;
}

//the fast, probabilistic algorithm has failed. fallback:
return companies.Where(cpy => cpy != src && cpy != plyr.PowerUpInAction.Passenger.Destination).OrderBy(pu => rand.Next()).ToList()[0];

In case we spin too often, we fall back to a safe algorithm.

Upvotes: 0

Related Questions