如何生成一个不产生超过X个连续元素的随机数序列

时间:2011-07-01 08:20:42

标签: c++ boost stl random

好吧,我真的不知道如何正确地解决这个问题,因为我几乎不知道如何用一句话描述我想要的东西而且我道歉。

让我直截了当地说你可以跳过剩下的原因我只想表明我已经尝试了一些东西而不是来这里随心所欲地提问。

我需要一种能产生6个随机数的算法,它可能不会在该序列中产生超过2个连续数字。

例如:3 3 4 4 2 1

^ FINE。

例如:3 3 3 4 4 2

^的 NO! 不! 错误!

显然,我不知道怎么做而不经常绊倒自己。

是否有可以执行此操作的STL或Boost功能?或者也许这里有人知道如何为它编制算法。那太棒了。

我正在尝试做什么以及我尝试过什么。(您可以跳过的部分)

这是在C ++中。我正试图制作一个Panel de Pon / Tetris攻击/益智联盟,无论如何克隆练习。游戏有一个6块的行,3个或更多的匹配块会破坏块。 Here's a video in case you're not familiar.

当一个新行来自底部时,它不能与3个水平匹配块一起出现,否则它将自动消失。我不想横向的东西。虽然垂直很好。

我试图完成这一点,看来我做不到。当我开始游戏时,块的块丢失了,因为它不应该检测到匹配。正如你所见,我的方法很可能很重,而且太复杂了。

enum BlockType {EMPTY, STAR, UP_TRIANGLE, DOWN_TRIANGLE, CIRCLE, HEART, DIAMOND};
vector<Block> BlockField::ConstructRow()
{
    vector<Block> row;

    int type = (rand() % 6)+1;

    for (int i=0;i<6;i++)
    {
        row.push_back(Block(type));
        type = (rand() % 6) +1;
    }

    // must be in order from last to first of the enumeration
    RowCheck(row, diamond_match);
    RowCheck(row, heart_match);
    RowCheck(row, circle_match);
    RowCheck(row, downtriangle_match);
    RowCheck(row, uptriangle_match);
    RowCheck(row, star_match);

    return row;
}

void BlockField::RowCheck(vector<Block> &row, Block blockCheckArray[3])
{
    vector<Block>::iterator block1 = row.begin();
    vector<Block>::iterator block2 = row.begin()+1;
    vector<Block>::iterator block3 = row.begin()+2;
    vector<Block>::iterator block4 = row.begin()+3;
    vector<Block>::iterator block5 = row.begin()+4;
    vector<Block>::iterator block6 = row.begin()+5;

    int bt1 = (*block1).BlockType();
    int bt2 = (*block2).BlockType();
    int bt3 = (*block3).BlockType();
    int bt4 = (*block4).BlockType();
    int type = 0;

    if (equal(block1, block4, blockCheckArray)) 
    {
        type = bt1 - 1;
        if (type <= 0) type = 6;
        (*block1).AssignBlockType(type);
    }
    else if (equal(block2, block5, blockCheckArray)) 
    {
        type = bt2 - 1;
        if (type <= 0) type = 6;
        (*block2).AssignBlockType(type);
    }
    else if (equal(block3, block6, blockCheckArray)) 
    {
        type = bt3 - 1;
        if (type == bt3) type--;
        if (type <= 0) type = 6;
        (*block3).AssignBlockType(type);
    }
    else if (equal(block4, row.end(), blockCheckArray)) 
    {
        type = bt4 - 1;
        if (type == bt3) type--;
        if (type <= 0) type = 6;

        (*block4).AssignBlockType(type);
    }
}

叹息,我不确定是否有助于展示这一点......至少它表明我尝试了一些东西。

基本上,我通过将BlockType枚举所描述的随机块类型分配给Block对象的构造函数(Block对象具有blockType和位置)来构造行。

然后我使用RowCheck函数来查看一行中是否有3个连续的blockTypes,并且我已经为所有块类型执行此操作。 * _match变量是具有相同块类型的3个Block对象的数组。如果我确实发现有3个连续的块类型,那么我只需简单地减去第一个值。但是,如果我这样做,我可能最终会无意中产生另一个3匹配,所以我只是确保块类型按顺序从最大到最小。

好吧,这很糟糕,它很复杂,但不起作用!这就是我需要你帮助的原因。

10 个答案:

答案 0 :(得分:5)

想法1。

while(sequence doesn't satisfy you)
      generate a new sequence 

想法没有2。

Precalculate all allowable sequences (there are about ~250K of them) 
randomly choose an index and take that element.

第二个想法需要很多记忆,但速度很快。第一个也不慢,因为你的while循环迭代次数超过一次或两次的概率很小。 HTH

答案 1 :(得分:5)

保留前两个值的记录就足够了,并且当新生成的值与之前的两个值匹配时循环。

对于任意运行长度,动态调整历史缓冲区大小并在循环中进行比较也是有意义的。但这应该接近您的要求。

int type, type_old, type_older;

type_older = (rand() % 6)+1;
row.push_back(Block(type_older));

type_old = (rand() % 6)+1;
row.push_back(Block(type_old));

for (int i=2; i<6; i++)
{
    type = (rand() % 6) +1;
    while ((type == type_old) && (type == type_older)) {
        type = (rand() % 6) +1;
    }

    row.push_back(Block(type));
    type_older = type_old;
    type_old = type;
}

答案 2 :(得分:5)

到目前为止看到的大多数解决方案涉及潜在的无限循环。我可以建议一个不同的approch吗?

// generates a random number between 1 and 6
// but never the same number three times in a row
int dice()
{
    static int a = -2;
    static int b = -1;
    int c;
    if (a != b)
    {
        // last two were different, pick any of the 6 numbers
        c = rand() % 6 + 1;
    }
    else
    {
        // last two were equal, so we need to choose from 5 numbers only
        c = rand() % 5;
        // prevent the same number from being generated again
        if (c == b) c = 6;
    }
    a = b;
    b = c;
    return c;
}

有趣的部分是else块。如果最后两个数字相等,则只有5个不同的数字可供选择,因此我使用rand() % 5代替rand() % 6。这个调用仍然可以产生相同的数字,并且它也不能产生6,所以我只是将该数字映射到6。

答案 3 :(得分:4)

使用简单的do-while循环的解决方案(对于大多数情况来说足够好):

vector<Block> row;

int type = (rand() % 6) + 1, new_type;
int repetition = 0;

for (int i = 0; i < 6; i++)
{
    row.push_back(Block(type));
    do {
        new_type = (rand() % 6) + 1;
    } while (repetition == MAX_REPETITION && new_type == type);

    repetition = new_type == type ? repetition + 1 : 0;
    type = new_type;
}

没有循环的解决方案(对于那些不喜欢先前解决方案的非确定性的人):

vector<Block> row;

int type = (rand() % 6) + 1, new_type;
int repetition = 0;

for (int i = 0; i < 6; i++)
{
    row.push_back(Block(type));

    if (repetition != MAX_REPETITION)
        new_type = (rand() % 6) + 1;
    else
    {
        new_type = (rand() % 5) + 1;
        if (new_type >= type)
            new_type++;
    }

    repetition = new_type == type ? repetition + 1 : 0;
    type = new_type;
}

在两种解决方案中,MAX_REPETITION等于1。

答案 4 :(得分:1)

如何将六元素数组初始化为[1, 2, 3, 4, 5, 6]并随机交换它们一段时间?这保证没有重复。

答案 5 :(得分:1)

很多答案都说“一旦你连续检测到Xs,重新计算最后一个X直到你没有得到X”......在这种游戏的实践中,这种方法比你快了几百万倍需要“实时”的人际互动,所以就这样做吧!

但是,你显然对它感到不舒服,并寻找更具有内在“优雅”的东西。因此,如果您从1..6生成数字,当您检测到2个X时,您已经知道下一个可能是重复的,因此只有5个有效值:生成1到5的随机数,如果是&gt; = X,再增加一个。

这有点像这样:

1..6 -> 3
1..6 -> 3
"oh no, we've got two 3s in a row"
1..5 -> ?
        < "X"/3   i.e. 1, 2       use as is
        >= "X"         3, 4, 5,   add 1 to produce 4, 5 or 6.

然后你知道最后两个元素不同......当你继续检查一行中的2个元素时,后者会占据第一个位置....

答案 6 :(得分:0)

vector<BlockType> constructRow()
{
    vector<BlockType> row;

    row.push_back(STAR); row.push_back(STAR);
    row.push_back(UP_TRIANGLE); row.push_back(UP_TRIANGLE);
    row.push_back(DOWN_TRIANGLE); row.push_back(DOWN_TRIANGLE);
    row.push_back(CIRCLE); row.push_back(CIRCLE);
    row.push_back(HEART); row.push_back(HEART);
    row.push_back(DIAMOND); row.push_back(DIAMOND);

    do
    {
        random_shuffle(row.begin(), row.end());
    }while(rowCheckFails(row));

    return row;
}

这个想法是在这里使用random_shuffle()。您需要实现满足要求的rowCheckFails()

修改

我可能无法正确理解您的要求。这就是我将每个块类型中的2个放入行中的原因。你可能需要投入更多。

答案 7 :(得分:0)

认为你可以更好地隐藏你的方法或功能背后的随机数生成。它可以是一次返回三个随机数的方法或函数,确保输出中至少有两个不同的数字。它也可以是一个流生成器,确保它不会连续输出三个相同的数字。

int[] get_random() {
    int[] ret;
    ret[0] = rand() % 6 + 1;
    ret[1] = rand() % 6 + 1;
    ret[2] = rand() % 6 + 1;

    if (ret[0] == ret[1] && ret[1] == ret[2]) {
        int replacement;
        do {
            replacement = rand() % 6 + 1;
        } while (replacement == ret[0]);
        ret[rand() % 3] = replacement;
    }
    return ret;
}

如果你想要六个随机数(这对我来说有点困难,而且视频只是令人费解:)那么生成if条件会更加努力:

for (int i=0; i<4; i++) {
    if (ret[i] == ret[i+1] && ret[i+1] == ret[i+2])
        /* three in a row */

如果您总是更改ret[1](三个中间),您将永远不会因为更改而有三行,但输出不会随机< / em>要么:X Y X会比X X Y更频繁地发生,因为它可能是偶然的机会通过在X X X的情况下被强制发生的。

答案 8 :(得分:0)

首先对上述解决方案进行一些评论。

  1. 如果不满意,那么涉及拒绝随机值的技术没有任何问题。这是拒绝采样的一个例子,这是一种广泛使用的技术。例如,用于生成随机高斯的若干算法涉及拒绝采样。一,极性拒绝方法,涉及从U(-1,1)重复绘制一对数字,直到两者都非零并且不位于单位圆之外。这导致超过21%的对。在找到令人满意的对之后,简单的变换产生一对高斯偏差。 (极地拒绝方法现在不再受欢迎,被ziggurat算法取代。它也使用拒绝采样。)

  2. rand() % 6出现了一些问题。不要这样做。永远。来自随机数发生器的低阶位,即使是一个好的随机数发生器,也不像高阶位那样“随机”。

  3. rand()期间有一些问题。大多数编译器编写者显然不知道有关生成随机数的bean。请勿使用rand()

  4. 现在使用Boost随机数库的解决方案:

    vector<Block> BlockField::ConstructRow(
      unsigned int max_run) // Maximum number of consecutive duplicates allowed
    {
      // The Mersenne Twister produces high quality random numbers ...
      boost::mt19937 rng;
    
      // ... but we want numbers between 1 and 6 ...
      boost::uniform_int<> six(1,6);
    
      // ... so we need to glue the rng to our desired output.
      boost::variate_generator<boost::mt19937&, boost::uniform_int<> >
        roll_die(rng, six);
    
      vector<Block> row;
    
      int prev = 0;
      int run_length = 0;
    
      for (int ii=0; ii<6; ++ii) {
        int next;
        do {
          next = roll_die();
          run_length = (next == prev) ? run_length+1 : 0;
        } while (run_length > max_run);
        row.push_back(Block(next));
        prev = next;
      }
    
      return row;
    }
    

答案 9 :(得分:0)

我知道这已有很多答案,但我想到了一个想法。你可以拥有7个数组,一个拥有全部6个数字,每个数组缺少一个给定的数字。像这样:

int v[7][6] = {
    {1, 2, 3, 4, 5, 6 },
    {2, 3, 4, 5, 6, 0 }, // zeros in here to make the code simpler, 
    {1, 3, 4, 5, 6, 0 }, // they are never used
    {1, 2, 4, 5, 6, 0 },
    {1, 2, 3, 5, 6, 0 },
    {1, 2, 3, 4, 6, 0 },
    {1, 2, 3, 4, 5, 0 }
};

然后你可以拥有2级历史。最后要生成一个数字,如果您的匹配历史记录小于最大值,则随机播放v [0]并取v [0] [0]。否则,将v [n]中的前5个值混洗并取v [n] [0]。像这样:

#include <algorithm>

int generate() {
    static int prev         = -1;
    static int repeat_count = 1;

    static int v[7][6] = {
        {1, 2, 3, 4, 5, 6 },
        {2, 3, 4, 5, 6, 0 }, // zeros in here to make the code simpler, 
        {1, 3, 4, 5, 6, 0 }, // they are never used
        {1, 2, 4, 5, 6, 0 },
        {1, 2, 3, 5, 6, 0 },
        {1, 2, 3, 4, 6, 0 },
        {1, 2, 3, 4, 5, 0 }
    };

    int r;

    if(repeat_count < 2) {
        std::random_shuffle(v[0], v[0] + 6);
        r = v[0][0];
    } else {
        std::random_shuffle(v[prev], v[prev] + 5);
        r = v[prev][0];
    }

    if(r == prev) {
        ++repeat_count;
    } else {
        repeat_count = 1;
    }

    prev = r;   
    return r;
}

这应该会产生良好的随机性(不依赖rand() % N),没有无限循环,并且考虑到我们每次都在洗牌的数量很少,应该是相当有效的。

注意,由于使用了静态,这是不是线程安全的,这对你的用法可能没问题,如果不是,那么你可能想把它包装在一个对象中,每个都有自己的状态。