假设我们有一个整数数组(3x3),如下所示:
+-+-+-+
| |1| |
+-+-+-+
|0|x|1|
+-+-+-+
| |0| |
+-+-+-+
上面的(0,1)设置为1,而(1,0)是0等。
现在假设我发现自己在(1,1)(在x处),对我来说最简单的方法是提出我可以采取的所有方向(比如所有具有值0的方法)然后在那些选择一个?
我遇到的麻烦实际上是选择所有有效方向然后在这些方向中进行选择之间的步骤。我可以相当容易地分开这两个步骤,但我没有一个结合了两者的优雅解决方案。
E.g。我可以将每个单元格的值乘以表示1,2,4和8的值,或者将它们全部加在一起。这会给我我可以采取的方向,但如何在它们之间做出选择?此外,我可以轻松地随机化一个介于1和4之间的数字来选择一个方向,但如果该方向被“采取”,那么我必须再次随机化,但不包括失败的方向。
有什么想法吗?
答案 0 :(得分:2)
最快的解决方案可能是您发布的最后一个解决方案 - 随机选择方向,重复直到您获得有效方向。这将最多需要四次尝试(最坏的情况是只有一个有效的邻居)。更优雅的是迭代所有可能的方向,在每个有效的邻居上随机更新变量,例如这个伪代码:
c = 1
r = invalid
for i in neighbors:
if (valid[i]):
if (rand() <= 1. / c): r = i
++c
然后r
就是答案(c
是到目前为止找到的有效邻居的数量。)
答案 1 :(得分:1)
这是伪代码中非常巧妙的技巧
在此结束时,您将获得有效的指示(如果未找到,则为零)。如果有多个有效方向,则将以相同的概率选择它们。
答案 2 :(得分:0)
一种轻率的设计方式可能是这个(伪代码):
使用该位掩码作为以下数组的索引:
struct RandomData
{
size_t num_directions;
struct { signed int dx, dy; } deltas[4];
} random_data[16];
其中num_directions是开放邻居的数量,deltas[]
告诉您如何到达每个邻居。
这有很多繁琐的数据,但它消除了循环和分支。
更新:好的,出于某种原因,我遇到了让这个想法消失的问题。我责备对工作中的“数据驱动编程”进行了一定程度的灌输,因为这个非常简单的问题使我“更好地”了解数据驱动的思想。这总是很好。
无论如何,这是使用上述想法的随机步进功能的完整,经过测试和工作的实现:
/* Directions are ordered from north and going clockwise, and assigned to bits:
*
* 3 2 1 0
* WEST | SOUTH | EAST | NORTH
* 8 4 2 1
*/
static void random_walk(unsigned int *px, unsigned int *py, unsigned max_x, unsigned int max_y)
{
const unsigned int x = *px, y = *py;
const unsigned int dirs = ((x > 0) << 3) | ((y < max_y) << 2) |
((x < max_x) << 1) | (y > 0);
static const struct
{
size_t num_dirs;
struct { int dx, dy; } deltas[4];
} step_info[] = {
#define STEP_NORTH { 0, -1 }
#define STEP_EAST { 1, 0 }
#define STEP_SOUTH { 0, 1 }
#define STEP_WEST { -1, 0 }
{ 0 },
{ 1, { STEP_NORTH } },
{ 1, { STEP_EAST } },
{ 2, { STEP_NORTH, STEP_EAST } },
{ 1, { STEP_SOUTH } },
{ 2, { STEP_NORTH, STEP_SOUTH } },
{ 2, { STEP_EAST, STEP_SOUTH } },
{ 3, { STEP_NORTH, STEP_EAST, STEP_SOUTH } },
{ 1, { STEP_WEST } },
{ 2, { STEP_NORTH, STEP_WEST } },
{ 2, { STEP_EAST, STEP_WEST } },
{ 3, { STEP_NORTH, STEP_EAST, STEP_WEST } },
{ 2, { STEP_SOUTH, STEP_WEST } },
{ 3, { STEP_NORTH, STEP_SOUTH, STEP_WEST } },
{ 3, { STEP_EAST, STEP_SOUTH, STEP_WEST } },
{ 4, { STEP_NORTH, STEP_EAST, STEP_SOUTH, STEP_WEST } }
};
const unsigned int step = rand() % step_info[dirs].num_dirs;
*px = x + step_info[dirs].deltas[step].dx;
*py = y + step_info[dirs].deltas[step].dy;
}
int main(void)
{
unsigned int w = 16, h = 16, x = w / 2, y = h / 2, i;
struct timeval t1, t2;
double seconds;
srand(time(NULL));
gettimeofday(&t1, NULL);
for(i = 0; i < 100000000; i++)
{
random_walk(&x, &y, w - 1, h - 1);
}
gettimeofday(&t2, NULL);
seconds = (t2.tv_sec - t1.tv_sec) + 1e-6 * (t2.tv_usec - t1.tv_usec);
printf("Took %u steps, final position is (%u,%u) after %.2g seconds => %.1f Msteps/second\n", i, x, y, seconds, (i / 1e6) / seconds);
return EXIT_SUCCESS;
}
有些解释可能是有序的,上面是非常不透明的,直到你“得到”它,我猜:
max_x
”和“max_y
”,以便在检查当前位置是否在边界上时保存一些常量减法。dirs
被设置为要进入的“打开”方向的位掩码。对于空网格,除非您在边界上,否则它总是0x0f。当然,这可以通过测试地图来处理墙壁。step_info
数组收集有关可能从16种可能的开放方向组合中采取哪些步骤的信息。在读取每个struct
的初始化(每行一个)时,请考虑该结构的二进制索引,并将其转换为dirs
中的位。STEP_NORTH
宏(以及朋友)减少了打字,并让它更清楚发生了什么。random_walk()
的“肉”只是四个几乎清晰的表达方式,在那里看不到一个if
令人耳目一新。在我的2.4 GHz x86_64系统上使用gcc(Ubuntu / Linaro 4.4.4-14ubuntu5)4.4.5编译时,使用优化级别-O3,性能似乎只有每秒3600万步。阅读程序集核心逻辑是无分支的。当然有一个rand()
的调用,我不想一路走下去并实现本地随机数生成器进行内联。
注意:这并不能解决所提出的确切问题,但我觉得这种技术值得继续扩展。
答案 3 :(得分:0)
假设:
每个位置都有一定数量的有效目标位置(某些位置的有效目标可能较少,国际象棋骑士放置在角落时的有效目标数量少于在棋盘中间位置的数量。)
您想从所有可用的有效移动中选择一个随机目标。
算法:
创建一个位数组,其中一位表示每个有效目标。 (在原始示例中,您将创建一个四位数组。)
对于每个有效目标,确定该位置是否为空;如果为空,则将位数组中的相应位设置为1。
如果位阵列&gt; 0然后number_of_targets = SUM(位数组),否则返回(无有效移动)。
在1和number_of_targets之间选择随机数。
返回(与位数组中第n个设置位相关的位置)
使用原始问题的示例:
X有四个有效的动作。我们创建一个4位数组,并为每个空位填充'1';从正上方的单元格开始,顺时针方向移动,我们最终得到:0:0:1:1:
比特总和告诉我们,我们有两个可以移动的地方。我们的随机选择将选择“1”或“2”。我们遍历位数组,直到找到第n个设置位并移动到该位置。
此算法适用于具有任意数量有效目标(不限于2-D)的任何系统。您可以使用递归返回最佳移动的函数(MIN-MAX算法)替换随机数选择器。