在这样的列表中:
var colors = new List<string>{"green", "red", "blue", "black","purple"};
我可以得到第一个这样的值:
var color = colors.First(c => c.StartsWidth("b")); //This will return the string with "blue"
如果我想要一个符合条件的随机值,我怎么做呢?例如:
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
如果匹配条件的列表中有多个条目,我想随机拉出其中一个条目。 它(我需要它)是一个内联解决方案。 谢谢。
答案 0 :(得分:15)
随机排序:
var rnd = new Random();
var color = colors.Where(c => c.StartsWith("b"))
.OrderBy(x => rnd.Next())
.First();
以上为每个元素生成一个随机数,并按该数字对结果进行排序。
如果您只有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));
}
答案 1 :(得分:4)
如果您有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();
答案 2 :(得分:3)
短序列的简单方法,如果您不介意迭代序列两次:
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)解决方案,但要求迭代序列一次以获得计数,然后再次选择一个随机项。
使用适合长序列的储层采样的更复杂的解决方案
您可以使用称为Reservoir Sampling
的方法,在一次通过(O(N))中从未知长度的序列中随机选择N个项目,而无需使用昂贵的排序。
您特别想在以下情况下使用水库采样:
虽然你也可以在其他情况下使用它。
以下是一个示例实现:
/// <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]);
但是,如果你想多次调用它而不是只调用一次,那么最好不要改组阵列并访问混洗数组中的前N个项目。 (很难说出问题的实际使用模式是什么。)
答案 3 :(得分:3)
另一个实现是提取所有可能的颜色( sample )并从中随机获取一个颜色:
// 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)];