Python的模块'random'有一个函数random.choice
random.choice(seq)
从非空序列seq返回一个随机元素。如果seq
为空,则引发IndexError
。
我如何在.NET中模拟这个?
public T RandomChoice<T> (IEnumerable<T> source)
答案 0 :(得分:13)
要创建一个只迭代源一次的方法,并且不必分配内存来临时存储它,您可以计算已迭代的项目数,并确定当前项应该是结果的概率: / p>
public T RandomChoice<T> (IEnumerable<T> source) {
Random rnd = new Random();
T result = default(T);
int cnt = 0;
foreach (T item in source) {
cnt++;
if (rnd.Next(cnt) == 0) {
result = item;
}
}
return result;
}
当你在第一个项目时,它应该被使用的概率为1/1(因为这是你见过的唯一项目)。当你在第二个项目时,它应该替换第一个项目的概率为1/2,依此类推。
这自然会使用更多的CPU,因为它为每个项目创建一个随机数,而不是像一个随机数来选择项目,正如dasblinkenlight所指出的那样。您可以像Dan Tao建议的那样检查源是否实现IList<T>
,并使用一个实现来使用这些功能来获取集合的长度并通过索引访问项目:
public T RandomChoice<T> (IEnumerable<T> source) {
IList<T> list = source as IList<T>;
if (list != null) {
// use list.Count and list[] to pick an item by random
} else {
// use implementation above
}
}
注意:您应该考虑将Random
实例发送到方法中。否则,如果您在时间上过于接近两次调用方法,则会获得相同的随机种子,因为种子是从当前时间创建的。
测试运行的结果,从包含0 - 9,1000000次的数组中选取一个数字,以显示所选数字的分布不会偏斜:
0: 100278
1: 99519
2: 99994
3: 100327
4: 99571
5: 99731
6: 100031
7: 100429
8: 99482
9: 100638
答案 1 :(得分:6)
为了避免遍历序列两次(一次用于计数,一次用于元素),在获取随机元素之前将序列保存在数组中可能是个好主意:
public static class RandomExt {
private static Random rnd = new Random();
public static T RandomChoice<T> (this IEnumerable<T> source) {
var arr = source.ToArray();
return arr[rnd.Next(arr.Length)];
}
public static T RandomChoice<T> (this ICollection<T> source) {
return source[rnd.Next(rnd.Count)];
}
}
答案 2 :(得分:2)
public T RandomChoice<T> (IEnumerable<T> source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var list = source.ToList();
if (list.Count < 1)
{
throw new MissingMemberException();
}
var rnd = new Random();
return list[rnd.Next(0, list.Count)];
}
或扩展程序
public static T RandomChoice<T> (this IEnumerable<T> source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var list = source.ToList();
if (list.Count < 1)
{
throw new MissingMemberException();
}
var rnd = new Random();
return list[rnd.Next(0, list.Count)];
}
答案 3 :(得分:1)
private static Random rng = new Random();
...
return source.Skip(rng.next(source.Count())).Take(1);
答案 4 :(得分:1)
我会使用dasblinkenlight's answer进行一项小改动:利用source
可能已经是索引集合的事实,在这种情况下,您实际上不需要填充新数组(或者列表):
public static class RandomExt
{
public static T Choice<T>(this Random random, IEnumerable<T> sequence)
{
var list = sequence as IList<T> ?? sequence.ToList();
return list[random.Next(list.Count)];
}
}
请注意,我还修改了上述答案中的界面,使其与您在问题中引用的Python版本更加一致:
var random = new Random();
var numbers = new int[] { 1, 2, 3 };
int randomNumber = random.Choice(numbers);
修改:实际上我更喜欢Guffa's answer。
答案 5 :(得分:0)
好吧,获取序列中所有元素的列表。问一个随机数生成器索引,通过索引返回elemnt。定义序列是什么 - IEnumerable最明显,但你需要将其表示为一个列表,然后知道随机数生成器的元素数量。 这是顺便说一句,不是模仿,而是实施。
这是一些家庭作业初学者学习课程的问题吗?
答案 6 :(得分:0)