How to use First () in LINQ, but arbitrarily?

In the following list:

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 can I do this if I want to get a random value that matches 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 if there are several entries in the list that matches the condition, I want one of them to be random. It (I need it) should be an embedded solution. Thank.

+4
source share
4 answers

Random ordering:

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 item and sorts the results by that number.

, , , 2 , . ( ):

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

:

b23
b73
b27
b11
b8

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

:

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

:

, ( ):

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));
}
+14

IList<T>, :

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

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

:

var color = colors.Where(c => c.StartsWith("b")).ToList().PickRandom();
+4

, :

var randomItem = sequence.Skip(rng.Next(sequence.Count())).First();

( ):

var colors = new List<string> { "bronze", "green", "red", "blue", "black", "purple", "brown" };
var rng = new Random();

for (int i = 0; i < 10; ++i)
{
    var sequence = colors.Where(c => c.StartsWith("b"));
    var randomItem = sequence.Skip(rng.Next(sequence.Count())).First();
    Console.WriteLine(randomItem);
}

O (N), , , , .


,

N (O (N)), , , Reservoir Sampling.

, :

  • -

.

:

/// <summary>
/// This uses Reservoir Sampling to select <paramref name="n"/> items from a sequence of items of unknown length.
/// The sequence must contain at least <paramref name="n"/> items.
/// </summary>
/// <typeparam name="T">The type of items in the sequence from which to randomly choose.</typeparam>
/// <param name="items">The sequence of items from which to randomly choose.</param>
/// <param name="n">The number of items to randomly choose<paramref name="items"/>.</param>
/// <param name="rng">A random number generator.</param>
/// <returns>The randomly chosen items.</returns>

public static T[] RandomlySelectedItems<T>(IEnumerable<T> items, int n, System.Random rng)
{
    var result = new T[n];
    int index = 0;
    int count = 0;

    foreach (var item in items)
    {
        if (index < n)
        {
            result[count++] = item;
        }
        else
        {
            int r = rng.Next(0, index + 1);

            if (r < n)
                result[r] = item;
        }

        ++index;
    }

    if (index < n)
        throw new ArgumentException("Input sequence too short");

    return result;
}

n 1, 1.

( , , , colors.Where(c => c.StartsWith("b") ):

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

for (int i = 0; i < 10; ++i)
    Console.WriteLine(RandomlySelectedItems(colors.Where(c => c.StartsWith("b")), 1, rng)[0]);

However, if you want to call this several times, and not just once, you would be better off shuffling the array and accessing the first N elements in the shuffled array. (It's hard to say what your actual usage model will be).

+3
source

Another implementation is to extract all possible colors (pattern) and make them random:

  // Simplest, but not thread safe
  private static Random random = new Random();  

  ...
  // All possible colors: [blue, black] 
  var sample = colors
    .Where(c => c.StartsWidth("b"))
    .ToArray();

  var color = sample[random.Next(sample.Length)]; 
+3
source

Source: https://habr.com/ru/post/1649249/


All Articles