我用来随机订购列表.我可以对枚举做同样的事情吗?

本文关键字:枚举 随机 我可以 列表 | 更新日期: 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;
    }
}