Как выбрать (действительную) случайную соседнюю точку в массиве целых чисел в C? - PullRequest
2 голосов
/ 21 января 2011

Предположим, что у нас есть массив целых чисел (3x3), изображенный следующим образом:

+-+-+-+
| |1| |
+-+-+-+
|0|x|1|
+-+-+-+
| |0| |
+-+-+-+

(0,1) выше установлено на 1, а (1,0) равно 0 и т. Д.

Теперь предположим, что я нахожусь в точке (1,1) (в точке х), что было бы для меня самым простым способом найти все направления, которые я могу выбрать (скажем, все, которые имеют значение 0), а затем те выбирают один?

С чем у меня проблемы, так это на самом деле шаг между выбором всех допустимых направлений и выбором из них. Я могу сделать эти два шага довольно легко, но у меня нет решения, которое кажется элегантным и объединяет их.

например. Я могу умножить значение каждой ячейки на значение, представляющее 1,2,4 и 8 и / или их все вместе. это дало бы мне какие направления я могу выбрать, но как выбрать между ними? Также я могу легко рандомизировать число от 1 до 4, чтобы выбрать направление, но если это направление «взято», то мне придется снова рандомизировать, но исключить направление, которое не удалось.

Есть идеи?

Ответы [ 4 ]

2 голосов
/ 21 января 2011

Самое быстрое решение, вероятно, последнее, которое вы опубликовали - выбирайте направления случайным образом, повторяя, пока не получите действительное.Это займет не более четырех попыток (наихудший случай, когда есть только один действительный сосед).Что-то более элегантное - перебирать все возможные направления, произвольно обновляя переменную у каждого допустимого соседа, например, такой псевдокод:

c = 1
r = invalid
for i in neighbors:
  if (valid[i]):
    if (rand() <= 1. / c): r = i
    ++c

, а затем r - это ответ (c - это числок настоящему времени найдены действительные соседи).

1 голос
/ 21 января 2011

Вот очень аккуратный трюк в псевдокоде

  1. Инициализируйте ваш "текущий результат" равным нулю
  2. Инициализируйте "найденное число" равным 0
  3. Переберите всевозможные направления.Если он действителен, то:
    • инкремент "найденное число"
    • установить "текущий результат" в направлении с вероятностью 1 / "найденное число"

В конце этого у вас будет правильное направление (или ноль, если не найден).Если имеется несколько действительных направлений, все они будут выбраны с равной вероятностью.

0 голосов
/ 21 января 2011

Предполагается:

В каждой локации есть определенное количество действительных целевых локаций (в некоторых локациях может быть меньше действительных целей, у шахматного рыцаря меньше действительных целей, если они расположены в углу, чем в середине поля.доска.)

Вы хотите выбрать случайную цель из всех доступных, действительных ходов.


Алгоритм:

Создать битовый массив с одним битом, представляющимкаждая действительная цель.(В исходном примере вы создадите четырехбитный массив.)

Для каждой действительной цели определите, является ли местоположение пустым;установите соответствующий бит в массиве битов на 1, если он пуст.

Если массив битов> 0, то number_of_targets = SUM (массив битов), иначе верните (нет допустимых перемещений).

Выберите случайное число от 1 до number_of_targets.

return (расположение, связанное с битом n-го набора в массиве битов)


Пример с использованием исходного вопроса:

X имеет четыре действительных хода.Мы создаем 4-битный массив и заполняем его '1' для каждого пустого места;начиная с ячейки, расположенной прямо над ней, и двигаясь по часовой стрелке, мы получаем: 0: 0: 1: 1:

Сумма битов говорит о том, что у нас есть два места, которые мы можем переместить.Наш случайный выбор выберет «1» или «2».Мы перемещаемся по битовому массиву, пока не найдем n-й установленный бит и не переместимся в это место.

Этот алгоритм будет работать для любой системы с любым количеством действительных целей (не ограничиваясь 2-D).Вы можете заменить селектор случайных чисел функцией, которая рекурсивно возвращает лучший ход (алгоритм MIN-MAX.)

0 голосов
/ 21 января 2011

Слегка придуманный способ (псевдокод):

  1. Создайте битовую маску, как вы описываете, в зависимости от того, какие соседи открыты.
  2. Используйте эту битовую маску в качестве индекса в массиве:

    struct RandomData<br> {<br> size_t num_directions;<br> struct { signed int dx, dy; } deltas[4];<br> } 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.

При компиляции с помощью gcc (Ubuntu / Linaro 4.4.4-14ubuntu5) 4.4.5 на моей системе с частотой 2,4 ГГц x86_64 с использованием уровня оптимизации -O3 производительность, по-видимому, едва достигает 36 миллионов шагов в секунду. При чтении сборки ядро ​​логики не имеет ветвей. Конечно, есть звонок в rand(), мне не хотелось идти до конца и реализовывать локальный генератор случайных чисел, чтобы встроить.

ПРИМЕЧАНИЕ : Это не решает точный заданный вопрос, но я чувствовал, что технику стоит расширить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...