简体   繁体   English

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

[英]How to produce a random number sequence that doesn't produce more than X consecutive elements

Ok, I really don't know how to frame the question properly because I barely have any idea how to describe what I want in one sentence and I apologize.好的,我真的不知道如何正确地提出问题,因为我几乎不知道如何用一句话来描述我想要的东西,我很抱歉。

Let me get straight to the point and you can just skip the rest cause I just want to show that I've tried something and not coming here to ask a question on a whim.让我直奔主题,您可以跳过 rest 因为我只是想表明我已经尝试过一些东西,而不是一时兴起来这里问问题。

I need an algorithm that produces 6 random numbers where it may not produce more than 2 consecutive numbers in that sequence.我需要一个产生 6 个随机数的算法,它可能不会在该序列中产生超过 2 个连续数字。

example: 3 3 4 4 2 1示例:3 3 4 4 2 1

^FINE. ^很好。

example: 3 3 3 4 4 2示例:3 3 3 4 4 2

^ NO! ^不! NO!不! WRONG!错误的!

Obviously, I have no idea how to do this without tripping over myself constantly.显然,如果不经常绊倒自己,我不知道如何做到这一点。

Is there a STL or Boost feature that can do this?是否有 STL 或 Boost 功能可以做到这一点? Or maybe someone here knows how to concoct an algorithm for it.或者也许这里有人知道如何为它设计一个算法。 That would be awesome.那将是真棒。

What I'm trying to do and what I've tried.我正在尝试做的事情和我尝试过的事情。 (the part you can skip) (可以跳过的部分)

This is in C++.这是在 C++ 中。 I'm trying to make a Panel de Pon/Tetris Attack/Puzzle League whatever clone for practice.我正在尝试制作一个 Panel de Pon/Tetris Attack/Puzzle League 任何克隆进行练习。 The game has a 6 block row and 3 or more matching blocks will destroy the blocks.游戏有一个 6 块行和 3 个或更多匹配块将破坏块。 Here's a video in case you're not familiar.这是一个视频,以防您不熟悉。

When a new row comes from the bottom it must not come out with 3 horizontal matching blocks or else it will automatically disappear.当一个新行从底部出来时,它一定不能有 3 个水平匹配块,否则它会自动消失。 Something I do not want for horizontal.我不想要水平的东西。 Vertical is fine though.不过竖版也不错。

I've tried to accomplish just that and it appears I can't get it right.我试图做到这一点,但似乎我无法做到这一点。 When I start the game chunks of blocks are missing because it detects a match when it shouldn't.当我开始游戏时,缺少块块,因为它在不应该检测到匹配时。 My method is more than likely heavy handed and too convoluted as you'll see.如您所见,我的方法很可能过于笨拙且过于复杂。

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

Sigh , I'm not sure if it helps to show this...At least it shows that I've tried something.叹息,我不确定这是否有助于展示这一点......至少它表明我已经尝试过一些东西。

Basically, I construct the row by assigning random block types, described by the BlockType enum, to a Block object's constructor(a Block object has blockType and a position).基本上,我通过将 BlockType 枚举描述的随机块类型分配给块对象的构造函数来构造行(块 object 具有块类型和位置)。

Then I use a RowCheck function to see if there's 3 consecutive blockTypes in one row and I have do this for all block types.然后我使用 RowCheck function 来查看一行中是否有 3 个连续的块类型,并且我对所有块类型都这样做了。 The *_match variables are arrays of 3 Block objects with the same block type. *_match 变量是具有相同块类型的 3 个块对象的 arrays。 If I do find that there are 3 consecutive block types then, I just simply subtract the first value by one.如果我确实发现有 3 种连续的块类型,我只需将第一个值减一即可。 However if I do that I might end up inadvertently producing another 3 match so I just make sure the block types are going in order from greatest to least.但是,如果我这样做,我可能最终会无意中产生另一个 3 匹配,所以我只是确保块类型按从大到小的顺序排列。

Ok, it's crappy, it's convoluted and it doesn't work.好吧,这很糟糕,很复杂,而且不起作用。 That's why I need your help.这就是为什么我需要你的帮助。

Idea no 1.想法1。

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

Idea no 2.想法2。

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

The second idea requires much memory, but is fast.第二个想法需要很多 memory,但速度很快。 The first one isn't slow either because there is a veeery little probability that your while loop will iterate more than once or twice.第一个也不慢,因为您的 while 循环迭代不止一次或两次的可能性很小。 HTH HTH

It should suffice to keep record of the previous two values, and loop when the newly generated one matches both of the previous values.记录前两个值就足够了,并在新生成的值与前两个值匹配时循环。

For an arbitrary run length, it would make sense to size a history buffer on the fly and do the comparisons in a loop as well.对于任意运行长度,动态调整历史缓冲区的大小并在循环中进行比较是有意义的。 But this should be close to matching your requirements.但这应该接近满足您的要求。

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

Most solutions seen so far involve a potentially infinite loop.到目前为止看到的大多数解决方案都涉及潜在的无限循环。 May I suggest a different 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;
}

The interesting part is the else block.有趣的部分是 else 块。 If the last two numbers were equal, there is only 5 different numbers to choose from, so I use rand() % 5 instead of rand() % 6 .如果最后两个数字相等,则只有 5 个不同的数字可供选择,因此我使用rand() % 5而不是rand() % 6 This call could still produce the same number, and it also cannot produce the 6, so I simply map that number to 6.这个调用仍然可以产生相同的数字,它也不能产生 6,所以我简单地将 map 那个数字改为 6。

Solution with simple do-while loop (good enough for most cases):使用简单的 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;
}

Solution without loop (for those who dislike non-deterministic nature of previous solution):没有循环的解决方案(对于那些不喜欢先前解决方案的不确定性的人):

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

In both solutions MAX_REPETITION is equal to 1 for your case.在这两种解决方案中,对于您的情况,MAX_REPETITION 都等于 1。

How about initializing a six element array to [1, 2, 3, 4, 5, 6] and randomly interchanging them for awhile?如何将一个六元素数组初始化为[1, 2, 3, 4, 5, 6]并随机交换它们一段时间? That is guaranteed to have no duplicates.保证没有重复。

Lots of answers say "once you detect Xs in a row, recalculate the last one until you don't get an X".... In practice for a game like this, that approach is millions of times faster than you need for "real-time" human interaction, so just do it!很多答案说“一旦你连续检测到 X,重新计算最后一个,直到你没有得到 X”......实际上,对于这样的游戏,这种方法比你需要的要快数百万倍“实时”的人际互动,所以就去做吧!

But, you're obviously uncomfortable with it and looking for something more inherently "bounded" and elegant.但是,你显然对它感到不舒服,并寻找更本质上“有界”和优雅的东西。 So, given you're generating numbers from 1..6, when you detect 2 Xs you already know the next one could be a duplicate, so there are only 5 valid values: generate a random number from 1 to 5, and if it's >= X, increment it by one more.因此,假设您从 1..6 生成数字,当您检测到 2 个 X 时,您已经知道下一个 X 可能是重复的,因此只有 5 个有效值:生成一个从 1 到 5 的随机数,如果是>= X,再增加一。

That works a bit like this:这有点像这样:

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.

Then you know the last two elements differ... the latter would take up the first spot when you resume checking for 2 elements in a row....然后你知道最后两个元素不同......当你继续检查连续两个元素时,后者将占据第一个位置......

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

The idea is to use random_shuffle() here.这个想法是在这里使用random_shuffle() You need to implement rowCheckFails() that satisfies the requirement.您需要实现满足要求的rowCheckFails()

EDIT编辑

I may not understand your requirement properly.我可能无法正确理解您的要求。 That's why I've put 2 of each block type in the row.这就是为什么我将每种块类型中的 2 个放在行中。 You may need to put more.你可能需要放更多。

I think you would be better served to hide your random number generation behind a method or function.认为最好将随机数生成隐藏在方法或 function 后面。 It could be a method or function that returns three random numbers at once, making sure that there are at least two distinct numbers in your output.它可以是一个方法或 function 一次返回三个随机数,确保 output 中至少有两个不同的数字。 It could also be a stream generator that makes sure that it never outputs three identical numbers in a row.它也可以是一个 stream 生成器,确保它永远不会连续输出三个相同的数字。

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 you wanted six random numbers (it's a little difficult for me to tell, and the video was just baffling:) then it'll be a little more effort to generate the if condition:如果你想要六个随机数(这对我来说有点难以分辨,而且视频只是令人费解:) 那么生成if条件会更加努力:

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

If you always change ret[1] (the middle of the three) you'll never have three-in-a-row as a result of the change, but the output won't be random either: XYX will happen more often than XXY because it can happen by random chance and by being forced in the event of XXX .如果您总是更改ret[1] (三者的中间),那么您将永远不会因为更改而获得三合一,但 output 也不会是随机的: XYX将比XXY因为它可能是随机发生的,也可能是在XXX事件被迫发生的。

First some comments on the above solutions.首先对上述解决方案进行一些评论。

  1. There is nothing wrong with the techniques that involve rejecting a random value if it isn't satisfactory.如果随机值不令人满意,则拒绝随机值的技术没有任何问题。 This is an example of rejection sampling, a widely used technique.这是拒绝抽样的一个例子,这是一种广泛使用的技术。 For example, several algorithms for generating a random gaussian involve rejection sampling.例如,用于生成随机高斯的几种算法都涉及拒绝采样。 One, the polar rejection method, involves repeatedly drawing a pair of numbers from U(-1,1) until both are non-zero and do not lie outside the unit circle.一种是极坐标拒绝法,它涉及从 U(-1,1) 重复绘制一对数字,直到两者都不为零且不在单位圆之外。 This throws out over 21% of the pairs.这抛出了超过 21% 的对。 After finding a satisfactory pair, a simple transformation yields a pair of gaussian deviates.找到满意的对后,简单的变换会产生一对高斯偏差。 (The polar rejection method is now falling out of favor, being replaced by the ziggurat algorithm. That too uses a rejection sampling.) (极坐标拒绝方法现在已经失宠,被 ziggurat 算法所取代。这也使用了拒绝采样。)

  2. There is something very much wrong with rand() % 6 . rand() % 6有很多问题。 Don't do this.不要这样做。 Ever.曾经。 The low order bits from a random number generator, even a good random number generator, are not quite as "random" as are the high order bits.来自随机数发生器的低位比特,即使是一个好的随机数发生器,也不像高位比特那样“随机”。

  3. There is something very much wrong with rand() , period. rand()有很多问题,句号。 Most compiler writers apparently don't know beans about producing random numbers.大多数编译器编写者显然不知道有关生成随机数的 bean。 Don't use rand() .不要使用rand()

Now a solution that uses the Boost random number library:现在一个使用 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;
}

I know that this already has many answers, but a thought just occurred to me.我知道这已经有很多答案了,但是我突然想到了一个想法。 You could have 7 arrays, one with all 6 digits, and one for each missing a given digit.您可能有 7 个 arrays,一个包含所有 6 个数字,每个缺少一个给定数字。 Like this:像这样:

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

Then you can have a 2 level history.然后你可以有一个2级历史。 Finally to generate a number, if your match history is less than the max, shuffle v[0] and take v[0][0].最后生成一个数字,如果您的匹配历史小于最大值,则随机播放 v[0] 并取 v[0][0]。 Otherwise, shuffle the first 5 values from v[n] and take v[n][0].否则,将 v[n] 中的前 5 个值打乱并取 v[n][0]。 Something like this:像这样的东西:

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

This should result in good randomness (not reliant of rand() % N ), no infinite loops, and should be fairly efficient given the small amount of numbers that we are shuffling each time.这应该会导致良好的随机性(不依赖于rand() % N ),没有无限循环,并且考虑到我们每次洗牌的少量数字应该相当有效。

Note, due to the use of statics, this is not thread safe , that may be fine for your usages, if it is not, then you probably want to wrap this up in an object, each with its own state.请注意,由于使用了静态,这不是线程安全的,这对您的使用可能没问题,如果不是,那么您可能希望将其包装在 object 中,每个都有自己的 state。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM