【问题标题】:only return random number when it is unique仅在唯一时返回随机数
【发布时间】:2011-01-05 15:32:27
【问题描述】:

今天我的大脑正在融化,我想不出如何编写这么简单的代码。 numberList 是一串数字,由逗号分隔,如“2、34、10”等。当我请求一个随机数时,我需要检查字符串是否有数字,如果有,我想继续请求一个随机数,直到随机数绝对不在字符串中。我想不出我会做什么样的循环来让它工作:

Random r = new Random();

public int RandomPos(int max) {
  int i;
  do {
    i = r.Next(max) + 1;
  }
  while (!numberList.Contains(i.ToString()));

    return i;
}

【问题讨论】:

  • 这有什么问题? (顺便说一句,我为糟糕的语法道歉)
  • @Greg 一方面它不起作用,如果一个数字例如'3' 是另一个数字的一​​部分,例如'34'
  • 看我的回答....我认为你应该跳过!,因为他想拿第一个不在列表中的人。
  • 当心 - 包含在这里会给出误报;例如,“4,20,7”包含“2”。我自己会得到整数。
  • 为什么 numberList 是 String 而不是 List(或 HashSet)?

标签: c# loops random


【解决方案1】:

我只是用文字而不是代码来解释,因为我现在懒得写代码:

  1. 使用String.Split 将您的列表分解为一个数组,然后(如果需要)将其解析为整数。
  2. 使用Enumerable.Range(0, max).ToArray() 创建一个您可以选择的所有号码的列表。
  3. 从第二个列表中减去第一个列表。
  4. 从最终列表中随机选择一个元素。

这样做的好处是,您无需继续随机挑选事物并在可能无限但实际上并非实际的循环中重试。

编辑:这里有一些代码

string[] invalid = numberList.Split(", ");
var list = Enumerable.Range(0, max).Where(x => !invalid.Contains(x.ToString())).ToArray();
return list[r.Next(list.Count)];

【讨论】:

  • +1 表示处理大量无效值的好方法(通常在所有情况下都是一个好的清除方法)
  • 创建这个大数组并减去这 2 个数组的可能性会大大增加计算量。 (假设随机数生成器是正确的)
  • @Yochai 正确,只有当不允许的值比例很高时,这种方法才是最好的。
  • 我认为您的拆分需要修改。我知道没有拆分接受字符串。不过试试Split(new[]{',',' '},StringSplitOptions.RemoveEmptyEntries)
  • @Brad 啊,你是对的。我被困在 PHP 领域。我希望这种过载确实存在。
【解决方案2】:

删除!

  do 
  {
    i = r.Next(max) + 1;
  }
  while (numberList.Contains(i.ToString()));

【讨论】:

  • +1 是最简单的答案(尽管忘记了 numberlist 是一个字符串)。
  • 坏了,不幸的是:例如,如果 numberList"10,20,30" 那么这将永远不会返回任何 0, 1, @ 987654325@,3102030,即使 rng 生成它们。
  • 我会给 +1,除非没有检查是否可以找到不在列表中的号码。如果 numberList 在 is 中有数字 1-10 并且调用了 RandomPos(10) 怎么办?
【解决方案3】:

试试这个:

static string invalidNumbers = "0,1,2,3,4,5";
static Random random = new Random();

static int Randomize()
{
    var randomInt = random.Next(0, 10);
    if (!invalidNumbers.Split(',').Contains(randomInt.ToString()))
    {
        return randomInt;
    }
    else
    {
        return Randomize();
    }
}

【讨论】:

  • 为什么要重试?只需从一个范围中减去无效值,然后您只需要选择一次。
  • 我认为 while 循环会比递归更好,因为在极端情况下这可能会导致堆栈溢出(取决于它的优化方式),而 while 循环不会。不过,我不是 100% 确定这一点...... :)
  • @Tesserex :在这种情况下它会起作用。但是你如何从一个范围中减去“0,5,9”之类的东西呢?也许我现在太累了,看不到解决方案 =)
  • @Chris:完全是我的想法。堆栈溢出的机会随着列表中每个添加的数字而增加。
  • @Tesserex:我怀疑这取决于您的输入范围。如果冲突的可能性很低(例如,大样本大小和低禁止大小),那么与创建有效值集的开销相比,通常试错可能更有效。当然,如果您发生碰撞的机会很高(例如,范围为 1 到 1,000,000,其中禁止 1 到 999,999,那么您几乎肯定最好创建“允许”集合。
【解决方案4】:

提供一个简单的答案,您不需要Split()。这假定数字之间没有空格,请相应地修改:

String modifiedNumberList = "," + numberList + ",";
do {
    i = r.Next(max) + 1;
}
while (modifiedNumberList.Contains("," + i.ToString() + ","));

编辑:我相信 BrokenGlass 也是正确的,您不应该将“!”从我的解决方案中删除。

【讨论】:

    【解决方案5】:

    也许这就是你想要的?我使用了普通的while,因为我认为它们更容易阅读,我认为你唯一弄错的是!

    public int RandomPos(int max) {
      int i = r.Next(max);
      var intList = numberList.Split(',').ToDictionary<string,int>((n) => int.Parse(n));
      while(intList.Contains(i))
      {
         i = r.Next(max);
      }
      return i;
    }
    

    假设我需要先将 numberList 拆分为它们是否在字符串中。这将使第三行看起来像:

    【讨论】:

    • 不能使用 contains 进行部分匹配,您可能需要 .Split 字符串并对列表执行 .Find
    • while 上缺少括号。
    • 首先,如果您已经在创建 intList ... 在循环中使用它。其次,我建议: Dictionary intDict = numberList.Split(',').ToDictionary((n) => int.Parse(n)); ...为了更快的关键搜索
    • @Yochai:如果numberList 相当小,则普通数组或列表可能会比字典快。对于更大的数字集,字典 - 或者更好的是哈希集 - 肯定会更可取。
    • @Yochai,第一部分是错字,第二部分是个好点。
    【解决方案6】:

    @Dave 回复的修改:

    static string invalidNumbers = "0,1,2,3,4,5";
    static Random random = new Random();
    
    static int Randomize()
    {
    
        var randomInt = random.Next(0, 10);
        var splitString = invalidNumbers.Split(',');
    
        while (splitString.Contains(randomInt.ToString()))
        {
            randomInt = random.Next(0, 10);
        }
        return randomInt;        
    }
    

    【讨论】:

      【解决方案7】:

      改善这一点的几种方法:

      1) 使用List&lt;int&gt; 或其他东西代替字符串,让您的生活更轻松

      2) 如果max 很小(比如

      随着“已使用”数字的数量接近“最大值”,您可能会进入一个很长的循环,然后才能获得未使用的数字。对于超过几百个的最大值,这实际上可能是重要的。在您的情况下,这可能是问题,也可能不是问题。

      【讨论】:

        【解决方案8】:

        此代码将涵盖所有情况:

        1. “1,2,3,4,5”...
        2. “1, 2, 3,4,5”...

          private static int GetRandomNumber(string existingNumbers, int max)
          {
              string[] existingNumbersArray = existingNumbers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
              List<string> existingNumbersList = new List<string>();
          
          
          
          foreach (string number in existingNumbersArray)
          {
              existingNumbersList.Add(number.Trim());
          }
          
          
          while (true)
          {
              Random rnd = new Random();
              int value = rnd.Next(max);
          
          
              if (!existingNumbersList.Contains(value.ToString()))
              {
                  return value;
              }
          }
          
          }

        你甚至可以取出这部分:

                string[] existingNumbersArray = existingNumbers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                List<string> existingNumbersList = new List<string>();
        
                foreach (string number in existingNumbersArray)
                {
                    existingNumbersList.Add(number.Trim());
                }
        

        所以每次调用 GetRandomNumber 函数时都不会调用它。

        【讨论】:

          【解决方案9】:

          我对所有其他答案的补充..

              const string invalidNumbers = "0,1,2,3,4,5";
              Random random = new Random();
              int value = 0;
          
              List<int> tmpList = new List<int>();
              foreach (var x in invalidNumbers.Split(','))
              {
                  tmpList.Add(Int32.Parse(x));    
              }
          
              do
              {
                  value = random.Next(0, 10);
              } 
              while (tmpList.Contains(value));
          
              return value
          

          【讨论】:

            【解决方案10】:

            编辑:误解了第一篇文章的问题,无论如何,这是一个递归解决方案。

            将数字保留在列表中似乎更好,但如果需要按照您要求的格式,这里是:

                const int MAX_ATTEMPTS = 10;
                Random r = new Random();
                string nlist = "2, 34, 10";
            
                public int RandomPos(int max_val)
                {
                    List<string> used = nlist.Replace(" ","").Split(',').ToList();
                    return _RandomPos(MAX_ATTEMPTS, max_val, used);
                }
            
                private int _RandomPos(int tl, int max, List<string> used)
                {
                    if (tl <= 0)
                        throw new Exception("Could not generate random number. Too many tries.");
                    else
                    {
                        int rnum = r.Next(max);
                        if (!used.Contains(rnum.ToString()))
                        {
                            nlist += ", " + rnum.ToString();
                            return rnum;
                        }
                        else
                            return _RandomPos(tl - 1, max, used);
                    }
                }
            

            【讨论】:

              【解决方案11】:

              我意识到有很多条目,但我没有看到任何一些像样的错误检查。话虽如此,这将提供一些东西:

              • 在没有什么可以取消资格的情况下不会浪费任何努力
              • 只会从一系列可能的选项中进行选择
              • 如果无法在最大范围内选择数字且不在取消资格列表中,将标记 -1

              所以这里是:

              public int RandomPos(int max)
              {
                  // compile the list of numbers we need to disqualify
                  List<int> disqualified = numberList.Split(new[]{',',' '},StringSplitOptions.RemoveEmptyEntries).Select(n => int.Parse(n)).ToList();
              
                  // Nothing to check against, save the CPU cycles
                  if (disqualified.Count == 0)
                      return (new Random(DateTime.Now.Millisecond)).Next(max) + 1;
              
                  // make a list of everything that's possible for a choice
                  List<int> valid = Enumerable.Range(0, max).Where(r => !disqualified.Contains(r)).ToList();
              
                  // return either a valid result, or -1 if there are no valid results
                  return (valid.Count == 0 ? -1 : valid[(new Random(DateTime.Now.Millisecond)).Next() % valid.Count]);
              }
              

              【讨论】:

                【解决方案12】:

                .Split 可以完成工作,以下代码也可以工作,只是为了好玩(替换代码中的 while (!numberList.Contains(i.ToString())); 行,而不是检查 i.ToString() 检查","+i.ToString()+"," PLUS开头和结尾。如果","后面有空格需要调整一下):

                while (!numberList.StartsWith(i.ToString()+",")&&
                      !numberList.Contains(","+i.ToString()+",")&&
                      !numberList.EndsWith(","+i.ToString()));
                

                【讨论】:

                  【解决方案13】:
                  // if numberList is large then use HashSet<int> rather than a plain int[] array
                  int[] nums = numberList.Split(new[] { ',', ' ' },
                                                StringSplitOptions.RemoveEmptyEntries)
                                         .Select(int.Parse)
                                         .ToArray();
                  
                  int i;
                  while (nums.Contains(i = r.Next(max) + 1));
                  return i;
                  

                  (如果/当numberList 包含 rng 可能产生的所有可能值时,您还应该添加一个检查以确保您不会陷入无限循环。)

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2021-06-18
                    • 1970-01-01
                    • 2016-02-05
                    • 2013-11-20
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多