我用来随机订购列表.我可以对枚举做同样的事情吗?
本文关键字:枚举 随机 我可以 列表 | 更新日期: 2023-09-27 18:36:42
这是我在列表中随机排序前 N 个元素的代码:
int upper = 1;
if (myList.Count > 1)
{
Random r = new Random();
upper = Math.Min(maxNumberOfPackets, myList.Count);
for (int i = 0; i < upper; i++)
{
int randInd = r.Next(i, myList.Count);
var temp = myList[i];
myList[i] = myList[randInd];
myList[randInd] = temp;
}
}
好吧,现在我有"必要"只使用枚举(出于某种原因;所以我不想在枚举中转换它)。
据您所知,我可以对枚举执行相同的操作吗?我认为痛苦是进入 X 位置的元素......
我很好奇...
对于IEnumerable[<T>]
源,您通常应该假设它们可以读取一次,当然,在您使用它之前,您无法可靠地知道长度。随机化通常需要缓冲。你不妨只做:
var list = source.ToList();
并且只需使用您现有的列表随机化代码。当然,如果您想保持输入和输出相似,则可以再次将该列表作为IEnumerable[<T>]
返回。
如果你真的想,你可以从开始就打乱:
static IEnumerable<T> ScrambleStart(this IEnumerable<T> source,
int numberToScramble)
{
using(var iter = source.GetEnumerator()) {
List<T> list = new List<T>();
while(iter.MoveNext()) {
list.Add(iter.Current);
if(list.Count == numberToScramble) break;
}
ScrambleList(list); // your existing code
foreach(var item in list) yield return item;
while(iter.MoveNext()) {
yield return iter.Current;
}
}
}
IEnumerable
表示"只向前游标",它可以返回流式传输的数据(例如,来自数据库)。因此,为了能够对可枚举对象进行排序(无论它是否随机),确实意味着您需要缓存它,仅仅是因为您需要具有所有值才能确定排序。也就是说,您可以使用Enumerable.OrderBy
运算符执行此操作,但同样,它将在幕后为您进行缓存。
var r = new Random();
IEnumerable sorted = myList.OrderBy(i => r.Next());
您应该使用水库采样。
你总是必须使用你的IEnumerable
以某种方式随机化它,因此你可以调用.ToList()
,并使用你的排序方法。
考虑一个IEnumerable
可以有无限数量的项目这一事实。
下面的实现将从原始输入中产生一个(伪)随机序列。(除了随机是伪随机)伪性是基于猜测序列的长度,这当然在大多数情况下是错误的。对于长度小于 maxJump 长度的序列,这将是随机的。如果事实证明元素的数量是maxJump值的一半或更多,那么它将增加以允许更高程度的随机性。
public IEnumerable<T> Randomize(this IEnumerable<T> source,int maxJump = 1000){
var cache = new List<T>();
var r = new Random();
var enumerator = source.GetEnumerator();
var totalCount = 1;
while(enumerator.MoveNext()){
var next = r.Next(0,maxJump);
if(next < cache.Count){
//the element we are searching for is in the cache
yield return cache[next];
cache.RemoveAt(next);
} else {
next = next - cache.Count;
do{
totalCount++;
if(next == 0){
//we've found the next element so yield
yield return enumerator.Current;
} else {
//not the element we are looking for so cache
cache.Insert(r.Next(0,cache.Count),enumerator.Current);
}
--next;
}while(next > 0 && enumerator.MoveNext());
//if we've reached half of the maxJump length
//increase the max to allow for higher randomness
if("*totalCount == maxJump){
maxJump *= 2;
}
}
}
//Yield the elements in the cache
//they are already randomized
foreach(var elem in cache){
yield return elem;
}
}
这有效,但对于您希望从中随机选择相对较少数量的元素的大型序列,效率极低(因为它遍历源序列的每个元素)。
/// <summary>Randomly selects items from a sequence.</summary>
/// <typeparam name="T">The type of the items in the sequence.</typeparam>
/// <param name="sequence">The sequence from which to randomly select items.</param>
/// <param name="count">The number of items to randomly select from the sequence.</param>
/// <param name="sequenceLength">The number of items in the sequence among which to randomly select.</param>
/// <param name="rng">The random number generator to use.</param>
/// <returns>A sequence of randomly selected items.</returns>
/// <remarks>This is an O(N) algorithm (N is the sequence length).</remarks>
public static IEnumerable<T> RandomlySelectedItems<T>(IEnumerable<T> sequence, int count, int sequenceLength, System.Random rng)
{
if (sequence == null)
{
throw new ArgumentNullException("sequence");
}
if (count < 0 || count > sequenceLength)
{
throw new ArgumentOutOfRangeException("count", count, "count must be between 0 and sequenceLength");
}
if (rng == null)
{
throw new ArgumentNullException("rng");
}
int available = sequenceLength;
int remaining = count;
var iterator = sequence.GetEnumerator();
for (int current = 0; current < sequenceLength; ++current)
{
iterator.MoveNext();
if (rng.NextDouble() < remaining/(double)available)
{
yield return iterator.Current;
--remaining;
}
--available;
}
}