【问题标题】:Picking 6 random unique numbers选择 6 个随机唯一数字
【发布时间】:2018-02-16 14:24:30
【问题描述】:

我在尝试使其正常工作时遇到问题。我打算在 1 和 49 之间选择 6 个唯一数字。我有一个正确执行此操作的函数,但努力检查数组是否存在重复和替换。

srand(static_cast<unsigned int>(time(NULL))); // Seeds a random number
int picked[6];
int number,i,j;
const int MAX_NUMBERS = 6;

for (i = 0; i < MAX_NUMBERS; i++)
{
    number = numberGen();
    for (int j = 0; j < MAX_NUMBERS; j++)
    {
        if (picked[i] == picked[j])
        {
            picked[j] = numberGen();
        }
    }

}

我的数字生成器只是创建一个介于 1 和 49 之间的随机数,我认为它可以正常工作。我刚刚开始使用 C++,任何帮助都会很棒

int numberGen()
{
 int number = rand();
 int target = (number % 49) + 1;

 return target;
}

【问题讨论】:

  • 如果我们谈论 C++,std::set 在检查现有值方面要好得多,RNG 有一个单独的子库
  • number 去哪儿了?您为什么打算用新生成的数字替换已经生成的数字?
  • 一个简单的选择:将数字 1-49 粘贴在向量中。 std::shuffle 向量。获取前 6 个数字。
  • @James Fogarty - 在这种情况下,向量或数组没有区别。将值填入std::array。随机播放。读取前 6 个值 - 也适用于此。

标签: c++


【解决方案1】:

让我们分解这段代码。

for (i = 0; i < MAX_NUMBERS; i++)

我们正在执行一个包含 6 次迭代的 for 循环。

number = numberGen();

我们正在生成一个新数字,并将其存储到变量number 中。此变量未在其他任何地方使用。

for (int j = 0; j < MAX_NUMBERS; j++)

我们再次循环遍历数组...

    if (picked[i] == picked[j])

检查两个值是否匹配(仅供参考,picked[n] == picked[n]始终匹配)

        picked[j] = numberGen();

如果它们匹配,则为 现有的值分配一个新的随机数。

这里更好的方法是消除重复值(如果存在),然后将其分配给您的数组。例如:

for (i = 0; i < MAX_NUMBERS; i++)
{
    bool isDuplicate = false;

    do
    {
        number = numberGen(); // Generate the number

        // Check for duplicates
        for (int j = 0; j < MAX_NUMBERS; j++)
        {
            if (number == picked[j])
            {
                isDuplicate = true;
                break; // Duplicate detected
            }
        }
    }
    while (isDuplicate); // equivalent to while(isDuplicate == true)

    picked[j] = number;
}

在这里,我们运行一个 do-while 循环。循环的第一次迭代将生成一个随机数,并检查它是否已在数组中重复。如果是,它会重新运行循环,直到找到非重复项。一旦循环中断,我们就有一个有效的、非重复的可用数字,然后我们将它分配给数组。

随着您的课程进展,将会有更好的解决方案可用。

【讨论】:

    【解决方案2】:

    高效方法:有限费舍尔-耶茨洗牌

    要从 m 池中提取 n 个数字,您需要 n 次调用 random 来使用这种方法(在您的情况下为 6),而不是在简单地改组整个数组或向量时使用 m-1(在您的情况下为 49)。所以下面显示的方法比简单地打乱整个数组更有效并且不需要任何重复检查

    1. 随机数可能会变得非常昂贵,所以我认为最好不要生成不必要的随机数。简单地多次运行 rand() 直到得出合适的数字似乎不是一个好主意。
    2. 在几乎所有可用数字都需要绘制的情况下,重复的仔细检查会变得特别昂贵
    3. 我希望它是有状态的,所以你实际请求的 49 中有多少个数字并不重要

    下面的解决方案不做任何重复检查,并为 n 个随机数准确地调用 rand() 次。因此,有必要对您的 numberGen 稍作修改。尽管您确实应该使用随机库函数而不是 rand()。

    下面的代码绘制所有数字,只是为了验证一切正常,但很容易看出你如何只绘制 6 个数字 :-)

    如果您需要重复绘制,您可以简单地添加一个 reset() 成员函数来再次设置 drawn = 0。然后向量处于洗牌状态,但这不会造成任何伤害。

    如果您负担不起std::vector.at() 中的范围检查,您当然可以轻松地将其替换为索引访问运算符[]。但我认为尝试使用 at() 代码是一个更好的选择,通过这种方式,您可以在绘制太多数字的情况下进行错误检查。

    用法: 使用构造函数创建一个 n_out_of_m 的类实例,该构造函数将可用数字的数量作为参数。

    重复调用draw()来绘制数字。

    如果您更频繁地调用 draw(),则数字可用 std::vector.at() 将抛出 out_of_range 异常,如果您不喜欢这种情况,则需要添加检查。

    我希望有人喜欢这种方法。

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <cstdlib>
    
    size_t numberGen(size_t limit)
    {
        size_t number = rand();
        size_t target = (number % limit) + 1;
        
        return target;
    }
    
    class n_out_of_m {
    public:
               n_out_of_m(int m) {numbers.reserve(m); for(int i=1; i<=m; ++i) numbers.push_back(i);}
        int    draw();
    private:
        std::vector<int> numbers;
        size_t drawn = 0;
    };
    
    int n_out_of_m::draw()
    {
        size_t index = numberGen(numbers.size()-drawn) - 1;
        std::swap(numbers.at(index), numbers.at(numbers.size()-drawn-1));
        drawn++;
        return numbers.at(numbers.size()-drawn);
    };
    
    int main(int argc, const char * argv[]) {
        n_out_of_m my_gen(49);
        for(int n=0; n<49; ++n)
           std::cout << n << "\t" << my_gen.draw() << "\n";
            
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      C++17 示例

      C++17 正好为此提供了一个算法(看图):

      std::sample

      template< class PopulationIterator, class SampleIterator,
                class Distance, class UniformRandomBitGenerator >
      SampleIterator sample( PopulationIterator first, PopulationIterator last,
                             SampleIterator out, Distance n, 
                             UniformRandomBitGenerator&& g);
      

      (C++17 起)

      从序列[first;]中选择n个元素最后)使得每个 可能的样本出现的概率相等,并写出那些 将选定的元素放入输出迭代器中。随机数是 使用随机数生成器 g 生成。 [...]

      constexpr int min_value = 1;
      constexpr int max_value = 49;
      constexpr int picked_size = 6;
      
      constexpr int size = max_value - min_value + 1;
      
      // fill array with [min value, max_value] sequence
      std::array<int, size> numbers{};
      std::iota(numbers.begin(), numbers.end(), min_value);
      
      // select 6 radom
      std::array<int, picked_size> picked{};
      std::sample(numbers.begin(), numbers.end(), picked.begin(), picked_size,
                  std::mt19937{std::random_device{}()});
      

      C++11 随机播放

      如果你还不能使用 C++17,那么这样做的方法是生成一个数组中的所有数字,打乱数组,然后选择数组中的前 6 个数字:

      // fill array with [min value, max_value] sequence
      std::array<int, size> numbers{};
      std::iota(numbers.begin(), numbers.end(), min_value);
      
      // shuffle the array
      std::random_device rd;
      std::mt19937 e{rd()};
      
      std::shuffle(numbers.begin(), numbers.end(), e);
      
      // (optional) copy the picked ones:
      
      std::array<int, picked_size> picked{};
      std::copy(numbers.begin(), numbers.begin() + picked_size, picked.begin());
      

      附注:请使用新的 C++11 随机库。并且更喜欢 std::array 到裸 C 数组。它们不会衰减为指针,并提供beginendsize 等方法。


      【讨论】:

        猜你喜欢
        • 2016-05-09
        • 1970-01-01
        • 2015-03-31
        • 1970-01-01
        • 2014-08-19
        • 1970-01-01
        • 2010-12-10
        • 2012-03-27
        • 1970-01-01
        相关资源
        最近更新 更多