【问题标题】:c# Select N elements from list uniformly without shufflec#均匀地从列表中选择N个元素而不打乱
【发布时间】:2018-02-22 03:05:46
【问题描述】:

我想从 N 个元素的列表中均匀地选择 K 个元素,而不是打乱列表。所以算法应该总是产生相同的结果。

例如

int N = 100;

List<int> myList = Enumerable.Range(0, N-1).ToList();

int K = 20;

这里我期待结果:0, 5, 10, 15, 20, ...

例如if K == 50我期待:0, 2, 4, 6, 8, ...

但我不知道如何解决它,例如K = 53?我仍然想从列表中统一获取元素,但在这种情况下,我们不能简单地从列表中获取第二个元素。简单的方法是打乱元素并从列表中获取第一个 K 元素,再次对列表进行排序,但在这种情况下,算法的结果每次都会产生不同的结果,我每次都需要相同的结果。

任何帮助将不胜感激。

【问题讨论】:

  • 如果你想要相同的结果,我会使用 Rand 方法给一个种子作为参数。没有提供种子时的 Rand 方法以 PC 时间为种子,每次得到不同的结果。每次测试时,提供种子都会给出相同的随机结果。
  • 您至少可以为K == 20 和/或K == 50 发布您的解决方案
  • 如果是 53,列表应该正好包含 53 个在 prev 和 next 之间具有相同距离的元素?
  • @jdweng OP 不是在寻找随机数。 OP 要求选择等距的 #k - 给定列表的元素 - 如果我理解正确的话。
  • 嗨,提供种子和洗牌元素不是我想要的,它不会产生统一的选择。

标签: c# algorithm list


【解决方案1】:

找出如果它们都是等距的索引会是什么,并四舍五入到最接近的整数。

int N = 100;

List<int> myList = Enumerable.Range(0, N).ToList();

double K = 53;

List<int> solutions=new List<int>();
for (int i=1;i<=K;i++)
{
    solutions.Add(myList[(int)Math.Round(i*(N/K)-1)]);
}

Console.WriteLine(solutions.Count);
Console.WriteLine(string.Join(", ", solutions));

查看示例:DEMO

编辑:Math.FloorMath.Round 效果更好,因为 Math.Round 给出了 N=5、K=3 的错误解决方案:

Math.Round->1,2,4
Math.Floor->0,2,4 

查看示例:DEMO2

【讨论】:

  • 修复了编译器错误。在我的手机上执行此操作...在括号中添加
  • 此外,您不能保证不会两次使用相同的元素。
  • 如果 K 小于 101,我相信你是,在这种情况下,这个问题是无意义的@PaulKertscher
  • @YairHalberstadt 忘记我的评论。我走错了路;)
  • 您好,改正代码后即可使用。谢谢阿甘。
【解决方案2】:

这个问题和画线算法一模一样:https://en.wikipedia.org/wiki/Line_drawing_algorithm

在你的情况下,你想画一条从 (0, 0) 到 (20, 52) 的线

【讨论】:

    【解决方案3】:

    您仍然可以随机播放列表。请看下面的例子

    var n = 100;
    var k = 53;
    
    var originalList = GetListWithNElements(n);
    var shuffledList = ShuffleList(originalList);
    var firstKElements = GetFirstKElements(k, shuffledList);
    
    [...]
    
    List<int> GetListWithNElements(int n)
    {
      return Enumerable.Range(0, n-1).ToList();
    }
    
    List<int> ShuffleList(List<int> originalList)
    {
      List<int> copy = originalList.ToList();
      List<int> result = new List<int>();
      Random randomGenerator = new Random(314159); 
    
      while(copy.Any())
      {
        int currentIndex = randomGenerator.Next(copy.Count);
        result.Add(copy[currentIndex]);
        copy.RemoveAt(currentIndex);
      }
    
      return result;
    }
    
    List<int> GetFirstKElements(int k, List<int> shuffledList)
    {
      return shuffledList.Take(k).ToList();
    }
    

    Random 使用常量种子进行初始化,因此每次都会产生完全相同的 "random" 值序列,因此您每次都将采用相同的元素。

    【讨论】:

    • 您确定这段代码会为N=100; K=20N=100;K=50 产生预期的结果。我不敢相信,因为您使用随机生成器来创建下一个索引,但预期的列表是有序的。我很确定排序不能保证每个元素到 prev 和 next 的距离请求 - 但这是我没有正确理解的问题的一部分。
    • @Verarind 不,这确实不会产生相同的结果。我将 OPs 要求读作“每次都获得相同的结果”,但 20 或 50 的结果并不相同。
    【解决方案4】:

    尝试以下:

            static void Main(string[] args)
            {
                const int N = 100;
                {
                    List<int> myList = Enumerable.Range(0, N - 1).ToList();
                    int seed = 5;
                    int numberOfItems = 50;
                    List<int> results = TakeNItems(myList, seed, numberOfItems);
                }
            }
            static List<int> TakeNItems(List<int> data, int seed, int numberOfItems)
            {
                Random rand = new Random(seed);
    
                return data.Select((x,i) =>  new { x = x, index = i, rand = rand.Next()})
                    .OrderBy(x => x.rand)
                    .Take(numberOfItems)
                    .OrderBy(x => x.index)
                    .Select(x => x.x)
                    .ToList();
            }
    

    【讨论】:

    • 得到的数字不是均匀分布的。 OP的请求与随机性无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-24
    • 1970-01-01
    • 2018-03-11
    • 2019-08-07
    • 1970-01-01
    • 2014-06-14
    • 1970-01-01
    相关资源
    最近更新 更多