Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use First() in LINQ but random?

Tags:

c#

linq

In a list like this:

var colors = new List<string>{"green", "red", "blue", "black","purple"};

I can get the first value like this:

var color = colors.First(c => c.StartsWidth("b")); //This will return the string with "blue"

Bot how do I do it, if I want want a random value matching the conditions? For example something like this:

Debug.log(colors.RandomFirst(c => c.StartsWidth("b"))) // Prints out black
Debug.log(colors.RandomFirst(c => c.StartsWidth("b"))) // Prints out black
Debug.log(colors.RandomFirst(c => c.StartsWidth("b"))) // Prints out blue
Debug.log(colors.RandomFirst(c => c.StartsWidth("b"))) // Prints out black

As in if there are multiple entries in the list matching the condition, i want to pull one of them randomly. It has (I need it to be) to be an inline solution. Thank you.

like image 375
Askerman Avatar asked Jul 27 '16 07:07

Askerman


2 Answers

Random ordering then:

var rnd = new Random();
var color = colors.Where(c => c.StartsWith("b"))
                  .OrderBy(x => rnd.Next())
                  .First();

The above generates a random number for each element and sorts the results by that number.

You propbably won't notice a random results if you have only 2 elements matching your condition. But you can try the below sample (using the extension method below):

var colors = Enumerable.Range(0, 100).Select(i => "b" + i);

var rnd = new Random();

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(colors.RandomFirst(x => x.StartsWith("b"), rnd));
}

Output:

b23
b73
b27
b11
b8

You can create an extension method out of this called RandomFirst:

public static class MyExtensions
{
    public static T RandomFirst<T>(this IEnumerable<T> source, Func<T, bool> predicate, 
                                                                                Random rnd)
    {
        return source.Where(predicate).OrderBy(i => rnd.Next()).First();
    }
}

Usage:

var rnd = new Random();
var color1 = colors.RandomFirst(x => x.StartsWith("b"), rnd);
var color2 = colors.RandomFirst(x => x.StartsWith("b"), rnd);
var color3 = colors.RandomFirst(x => x.StartsWith("b"), rnd);

Optimization:

If you're worried about performance, you can try this optimized method (cuts the time to half for large lists):

public static T RandomFirstOptimized<T>(this IEnumerable<T> source, 
                                        Func<T, bool> predicate, Random rnd)
{
    var matching = source.Where(predicate);

    int matchCount = matching.Count();
    if (matchCount == 0)
        matching.First(); // force the exception;

    return matching.ElementAt(rnd.Next(0, matchCount));
}
like image 107
Zein Makki Avatar answered Nov 15 '22 17:11

Zein Makki


In case you have IList<T> you could also write a tiny extension method to pick a random element:

static class IListExtensions
{
   private static Random _rnd = new Random();

   public static void PickRandom<T>(this IList<T> items) =>
       return items[_rnd.Next(items.Count)];
}

and use it like this:

var color = colors.Where(c => c.StartsWith("b")).ToList().PickRandom();
like image 39
bashis Avatar answered Nov 15 '22 17:11

bashis