如何生成不产生超过 X 个连续元素的随机数序列
好吧,我真的不知道如何正确地提出这个问题,因为我几乎不知道如何用一句话来描述我想要的东西,我很抱歉。
让我开门见山,你可以跳过剩下的,因为我只是想表明我已经尝试过一些东西,而不是来这里一时兴起问问题。
我需要一个生成 6 个随机数的算法,但在该序列中它可能不会生成超过 2 个连续数字。
例如:3 3 4 4 2 1
^FINE。
例如:3 3 3 4 4 2
^不! 不!错了!
显然,我不知道如何做到这一点而不不断地绊倒自己。
是否有 STL 或 Boost 功能可以做到这一点?或者也许这里有人知道如何为其设计一个算法。那太棒了。
我正在尝试做什么以及我已经尝试过什么。(您可以跳过的部分)
这是用 C++ 编写的。我正在尝试制作一个 Panel de Pon/Tetris Attack/Puzzle League 克隆来进行练习。游戏有 6 个方块行,3 个或更多匹配的方块将摧毁方块。 您不熟悉。
这里有一个视频,以防 从底部出来的时候一定不能出现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 个连续的块类型,并且我对所有块类型都执行了此操作。 *_match 变量是具有相同块类型的 3 个块对象的数组。如果我确实发现有 3 个连续的块类型,我只需将第一个值减 1 即可。然而,如果我这样做,我可能最终会无意中产生另外 3 个匹配,所以我只是确保块类型按从最大到最小的顺序排列。
好吧,它很蹩脚,很复杂,而且不起作用!这就是为什么我需要你的帮助。
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.
I need an algorithm that produces 6 random numbers where it may not produce more than 2 consecutive numbers in that sequence.
example: 3 3 4 4 2 1
^FINE.
example: 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? 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++. I'm trying to make a Panel de Pon/Tetris Attack/Puzzle League whatever clone for practice. The game has a 6 block row and 3 or more matching blocks will destroy the blocks. 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. 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).
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. The *_match variables are arrays of 3 Block objects with the same block type. If I do find that there are 3 consecutive block types then, I just simply subtract the first value by one. 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.
Ok, it's crappy, it's convoluted and it doesn't work! That's why I need your help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
记录前两个值就足够了,并在新生成的值与前两个值匹配时循环。
对于任意的运行长度,动态调整历史缓冲区的大小并在循环中进行比较是有意义的。但这应该接近满足您的要求。
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.
想法1。
想法2。
第二个想法需要大量内存,但速度很快。第一个也不慢,因为 while 循环迭代超过一次或两次的可能性非常小。华泰
Idea no 1.
Idea no 2.
The second idea requires much memory, but is fast. 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. HTH
迄今为止看到的大多数解决方案都涉及潜在的无限循环。我可以建议一种不同的方法吗?
有趣的部分是 else 块。如果最后两个数字相等,则只有 5 个不同的数字可供选择,因此我使用
rand() % 5
而不是rand() % 6
。这个调用仍然可以产生相同的数字,而且也不能产生 6,所以我只是将该数字映射到 6。Most solutions seen so far involve a potentially infinite loop. May I suggest a different approch?
The interesting part is the else block. If the last two numbers were equal, there is only 5 different numbers to choose from, so I use
rand() % 5
instead ofrand() % 6
. This call could still produce the same number, and it also cannot produce the 6, so I simply map that number to 6.具有简单 do-while 循环的解决方案(对于大多数情况来说足够好):
没有循环的解决方案(对于那些不喜欢先前解决方案的非确定性性质的人):
在这两种解决方案中,对于您的情况, MAX_REPETITION 都等于 1。
Solution with simple do-while loop (good enough for most cases):
Solution without loop (for those who dislike non-deterministic nature of previous solution):
In both solutions MAX_REPETITION is equal to 1 for your case.
将一个六元素数组初始化为
[1, 2, 3, 4, 5, 6]
并随机交换它们一段时间怎么样?这保证没有重复项。How about initializing a six element array to
[1, 2, 3, 4, 5, 6]
and randomly interchanging them for awhile? That is guaranteed to have no duplicates.很多答案都说“一旦您检测到连续的 X,请重新计算最后一个,直到您没有得到 X”......在这样的游戏的实践中,这种方法比您所需的速度快数百万倍“实时”人机交互,所以就去做吧!
但是,您显然对此感到不舒服,并正在寻找一些本质上更“有界”和优雅的东西。因此,假设您从 1..6 生成数字,当您检测到 2 个 X 时,您已经知道下一个可能是重复的,因此只有 5 个有效值:生成一个从 1 到 5 的随机数,如果是>= X,再加一。
这有点像这样:
然后你知道最后两个元素不同......当你继续检查连续两个元素时,后者将占据第一个位置......
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!
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.
That works a bit like this:
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....
这个想法是在这里使用
random_shuffle()
。您需要实现满足要求的rowCheckFails()
。编辑
我可能无法正确理解您的要求。这就是为什么我在行中放置了每种块类型 2 个的原因。您可能需要投入更多。
The idea is to use
random_shuffle()
here. You need to implementrowCheckFails()
that satisfies the requirement.EDIT
I may not understand your requirement properly. That's why I've put 2 of each block type in the row. You may need to put more.
我认为您最好将随机数生成隐藏在方法或函数后面。它可以是一次返回三个随机数的方法或函数,确保输出中至少有两个不同的数字。它也可以是一个流生成器,确保它永远不会连续输出三个相同的数字。
如果你想要六个随机数(这对我来说有点困难,而且视频令人困惑:),那么生成
if
条件会花费更多的精力:如果你总是改变
ret[1]
(三者的中间)由于更改,您永远不会获得三连胜,但输出不会是随机的 em> 要么:XY X
会发生比XX Y
更频繁,因为它可能是随机发生的,并且是在XX X
事件中被迫发生的。I think you would be better served to hide your random number generation behind a method or 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. It could also be a stream generator that makes sure that it never outputs three identical numbers in a row.
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 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:X Y X
will happen more often thanX X Y
because it can happen by random chance and by being forced in the event ofX X X
.首先对上述解决方案进行一些评论。
如果随机值不令人满意,则拒绝该随机值的技术没有任何问题。这是拒绝采样的一个例子,这是一种广泛使用的技术。例如,用于生成随机高斯的几种算法涉及拒绝采样。一种是极坐标拒绝法,涉及从 U(-1,1) 中重复抽取一对数字,直到两个数字都非零且不在单位圆之外。这会丢弃超过 21% 的配对。找到满意的一对后,简单的变换就会产生一对高斯偏差。 (极性拒绝方法现在已经不再受欢迎,被 Ziggurat 算法所取代。它也使用拒绝采样。)
rand() % 6
有一些非常错误的地方。不要这样做。曾经。随机数生成器的低阶位,即使是一个好的随机数生成器,也不像高阶位那么“随机”。rand()
有一些非常错误的地方。大多数编译器编写者显然不知道如何生成随机数。不要使用rand()
。现在使用Boost随机数库的解决方案:
First some comments on the above solutions.
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. This throws out over 21% of the pairs. 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.)
There is something very much wrong with
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.There is something very much wrong with
rand()
, period. Most compiler writers apparently don't know beans about producing random numbers. Don't userand()
.Now a solution that uses the Boost random number library:
我知道这已经有很多答案了,但我突然想到了一个想法。您可以有 7 个数组,其中一个包含所有 6 位数字,每个数组都有一个缺少给定的数字。像这样:
然后你就可以有2级历史记录了。最后生成一个数字,如果你的比赛历史小于最大值,则洗牌 v[0] 并取 v[0][0]。否则,将 v[n] 中的前 5 个值打乱并取 v[n][0]。像这样的事情:
这应该会产生良好的随机性(不依赖于 rand() % N),没有无限循环,并且考虑到我们每次洗牌的数字量较少,应该相当有效。
请注意,由于使用静态,这是不是线程安全的,这可能适合您的使用,如果不是,那么您可能希望将其包装在一个对象中,每个对象都有它的自己的状态。
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. Like this:
Then you can have a 2 level history. Finally to generate a number, if your match history is less than the max, shuffle v[0] and take v[0][0]. Otherwise, shuffle the first 5 values from v[n] and take v[n][0]. Something like this:
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.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.