【问题标题】:How can I pick random objects out of a list with C#?如何使用 C# 从列表中选择随机对象?
【发布时间】:2019-03-29 12:15:16
【问题描述】:

我有一个包含 300 多个对象的 IQueryable:

public class Detail
{
    public int Id { get; set; }
    public int CityId { get; set; }
    public bool Chosen { get; set; }
}

IQueryable<Detail> details = ...

我如何对抗这个随机挑选 50 个对象?我假设我需要用 .ToList() 转换它,但我不确定如何挑选随机元素。

【问题讨论】:

标签: c# linq


【解决方案1】:

300 不是很多,所以是的,把它做成一个 List:

IQueryable<Detail> details = ...
IList<Detail> detailList = details.ToList();

现在你可以选择一个随机项目:

var randomItem = detailList[rand.Next(detailList.Count)];

你可以重复 50 次。然而,这会导致重复,并且消除它们的过程会变得混乱。

所以使用 standard shuffle algorithm 然后选择前 50 个:

Shuffle(detailList);
var selection = detailList.Take(50);

【讨论】:

  • 使用 Shuffle 算法的方法多么有趣。我总是继续迭代,直到我得到 50 个独特的项目,但这显然是一个更糟糕的算法。非常感谢亨克!
【解决方案2】:
Random rnd = new Random();
IQueryable<Detail> details = myList.OrderBy(x => rnd.Next()).Take(50);

【讨论】:

    【解决方案3】:

    如果您事先知道要从中随机选择的项目总数,则无需先转换为列表即可。

    下面的方法会帮你搞定的:

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

    (这里的关键是一开始就需要知道可供选择的项目数量;这确实会在一定程度上降低实用性。但是如果获取计数很快并且缓冲所有项目会占用太多内存,那么这是一个有用的解决方案。)


    这是另一种使用Reservoir sampling的方法

    这种方法不需要知道可供选择的项目总数,但它确实需要缓冲输出。当然,它还需要枚举整个输入集合。

    因此,这仅在您事先不知道可供选择的项目数量(或可供选择的项目数量非常多)时使用。

    我建议按照 Henk 的回答对列表进行改组,而不是这样做,但为了感兴趣,我将其包括在此处:

    // n is the number of items to randomly choose.
    
    public static List<T> RandomlyChooseItems<T>(IEnumerable<T> items, int n, Random rng)
    {
        var result = new List<T>(n);
        int index = 0;
    
        foreach (var item in items)
        {
            if (index < n)
            {
                result.Add(item);
            }
            else
            {
                int r = rng.Next(0, index + 1);
    
                if (r < n)
                    result[r] = item;
            }
    
            ++index;
        }
    
        return result;
    }
    

    作为 Henk 答案的附录,这是他提到的 Shuffle 算法的规范实现。其中_rng 是Random 的一个实例:

    /// <summary>Shuffles the specified array.</summary>
    /// <typeparam name="T">The type of the array elements.</typeparam>
    /// <param name="array">The array to shuffle.</param>
    
    public void Shuffle<T>(IList<T> array)
    {
        for (int n = array.Count; n > 1;)
        {
            int k = _rng.Next(n);
            --n;
            T temp = array[n];
            array[n] = array[k];
            array[k] = temp;
        }
    }
    

    【讨论】:

    • 我认为这不会均匀分布随机项目(没有明确说明,但可以假设)。要查看这一点,请考虑第一个项目,它只能作为随机项目中的第一个被挑选出来,如果不是,它就不再是候选项目。
    • 不,它确实会平均分布。这是一个很好理解的算法。
    • 啊,是的。我在想它所比较的​​随机数只计算了一次(所以如果它是 0.5,它会跳过列表的前半部分),但实际上它每次都会生成一个新的。是的。我现在知道它是如何工作的。
    【解决方案4】:
    var l = new List<string>();
    l.Add("A");
    l.Add("B");
    l.Add("C");
    l.Add("D");
    l.Add("E");
    l.Add("F");
    l.Add("G");
    l.Add("H");
    l.Add("I");
    
    var random = new Random();
    var nl = l.Select(i=> new {Value=i,Index = random.Next()});
    
    var finalList = nl.OrderBy(i=>i.Index).Take(3);
    foreach(var i in finalList)
    {
        Console.WriteLine(i.Value);
    }
    

    【讨论】:

      【解决方案5】:
      IQueryable<Detail> details = myList.OrderBy(x => Guid.NewGuid()).ToList();
      

      在此之后,只需线性地穿过它:

      var item1 = details[0];
      

      这样可以避免重复。

      【讨论】:

      • 是的,Collections.Shuffle,您需要链接到该算法或将您的算法内联。
      • @neoistheone:呵呵,与 Java 库混淆了。 Henk 已经将算法链接起来,这就足够了。 Guid 方法绝对是最简单的。
      • 你知道,我想知道这一点,因为当我用谷歌搜索它时,我发现了一个 Java 库!
      • 很遗憾,它还没有内置到 C# 库中。你会认为这是一个相当基本的收集工具
      • 我必须同意你的观点,因为我已经看到了 shuffle 算法,因为它使它成为一个 O(n) 操作。我想从技术上讲,您将再迭代 50 次,但如果我没记错的话,这不会改变表示法。
      【解决方案6】:

      这最终对我有用,它确保不返回重复项:

      public List<T> GetRandomItems(List<T> items, int count = 3)
      {
          var length = items.Count();
          var list = new List<T>();
          var rnd = new Random();
          var seed = 0;
      
          while (list.Count() < count)
          {
              seed = rnd.Next(0, length);
              if(!list.Contains(items[seed]))
                  list.Add(items[seed]);
          }
      
          return list;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-11-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-27
        • 2013-10-19
        • 2023-01-11
        • 1970-01-01
        • 2021-06-10
        • 2018-09-16
        相关资源
        最近更新 更多