2013-07-15 2 views
1

У меня есть фрагмент кода, который генерирует мини-миссии для игроков. Это просто и получить две различные точки (начало и назначения) У меня есть алгоритм, который выглядит следующим образом:Как получить два (псевдо) случайных, но отличающихся друг от друга итераторами контейнеров/элементов?

std::vector<std::string> missions; 

    missions.push_back("Location_One"); 
    missions.push_back("Location_Two"); 
    missions.push_back("Location_Three"); 

    //make sure our data has at least 2 elements (so we can actually pick two) 
    if(missions.size() > 1) 
    { 
     //Rand(inclusive min, exlusive max) 
     int mission_start_location = Rand(0,missions.size()); 
     int mission_end_location = Rand(0,missions.size()); 

     if(mission_start_location == mission_end_location) 
     { 
      //avoid possile infinite loop of calling "Rand" by Add/Decrement-if-equal algorithm 
      //basicly if mission_start_location == 0 
      if(!mission_start_location) 
       ++mission_end_location;//or = 1, we have at least two elements so index 1 is valid 
      else 
       --mission_end_location;//so we won't got out of range 
     } 
     //do mission 
    } 
    else 
    { 
     //error 
    } 

Это работает, но мне было интересно, если есть лучший способ добиться того, чего я хочу " путь C++ ».

Мои вопросы:

  • Является ли это лучший способ, чтобы получить два различных значения из контейнера?
  • Что касается контейнеров с нецелым индексом (например, std::map<std::string,std::string>)?
    • Как бы получить из него два разных и случайных значения?

Примечание: Я хорошо осведомлены о методе do { } while(rand1 == rand2). Я хочу этого избежать, потому что может зайти в бесконечный цикл (зная, что мне повезет в производственном коде).

+0

Ваши два случайных значения не являются _independent_: 'mission_end_location == (mission_start_location-1)' более вероятен, чем другие варианты. Это, вероятно, не то, что вы намеревались? – jogojapan

+0

это единственный способ, которым я могу придумать, чтобы избежать того же «случайного» номера, не вызывая Rand() и бесконечное количество раз (например, как-то rand всегда будет возвращать 0, то что?) - не происходит, но я хочу избежать этого в будущем – Gizmo

+0

@jogojapan - вот что я хотел бы избежать, если возможно, – Gizmo

ответ

3

Да, вы можете иметь этот другой способ.

if(missions.size() > 1) 
    { 
    size_t const m_size = missions.size(); 
    // get random number in the full range 
    int const m_start = Rand(0, m_size); 
    // get another number in a range reduced by 1 
    int m_end = Rand(0, m_size-1); 
    // if we are equal or above start we shift by 1 up 
    if (m_end >= m_start) ++m_end; 
    } 
 
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 

Если ваш первый выбор выбирает 4, практически удалить его, имеющий второе случайное число на диапазоне-1.

 
| 0 | 1 | 2 | 3 | 4(was 5) | 5(was 6) | 6(was 7) | 

Чтобы отобразить второе случайное число обратно к первоначальной индексации просто добавьте 1, если вы выше первое случайное число, так как эти показатели были сдвинуты на 1 из-за «снятия» первого индекс.


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

0

• Это лучший способ получить два разных значения из контейнера?

Это будет генерировать разные значения, но вы можете получить индекс, который выходит за пределы диапазона, потому что вы увеличиваете и уменьшаете. Я бы предложил просто повторно создать одно из мест миссии, в то время как оно равно другому, а не увеличивать, поскольку это также устранит некоторое смещение, которое увеличивает причины. Кроме того, я не знаю, почему у вас есть блок if(!mission_start_location), чего вы пытаетесь достичь с этим?

EDIT Я видел, что вы беспокоились о бесконечных циклах. Тебе не должно быть. Это самый стандартный способ C++ для генерации неравных случайных чисел, и ваши шансы на ввод бесконечного цикла: крайне маловероятно, даже если ваш вектор имеет размер 2 (в этом случае потребуется в среднем 2 итерации, чтобы гарантировать что вы выбрали новый номер).


• Что о нецелая-указательных контейнеров (например, станд :: карта)?

• Как я могу получить от него два разных и случайных значения?

Большинство из них должны иметь итератор begin(). Вы можете использовать это, чтобы получить что-то случайное по , добавляя случайное значение от 0 до size() , увеличивая его в цикле for случайным числом раз (мое исходное решение не работает, потому что std::map begin() не разрешает оператор +, но позволяет ++) (см. this для справки для карты. Вы также можете найти здесь другие контейнеры).

+0

if (! mission_start_location) в основном, если (mission_start_location == 0), что означает, что и другое тоже ноль, и мы знаем, что у нас больше чем 1 элемент, поэтому мы добавляем один к mission_end_location, который эффективно становится равным 1. (не за пределами). Регенерация случайного значения может попасть в «бесконечный цикл», и это то, чего я хочу избежать! – Gizmo

+0

'std :: map' не предоставляет итератор с произвольным доступом. – jogojapan

+0

Хорошо, но зачем было 'mission_start_location == 0 -> mission_end_location == 0'? Это не всегда верно. В вашем примере ваш вектор имеет размер 3. Таким образом, 'mission_start_location' может быть' 0', а 'mission_end_location' может быть' 2'. Затем с вашим кодом вы увеличите значение 'mission_end_location' и получите индекс, который находится за пределами – wlyles

0

Прежде всего, то, что вы сейчас делаете, это один из способов использования C++, но не обязательно лучший.Давайте сначала рассмотрим код ниже, где большая часть логики:

... 
    int mission_start_location = Rand(0,missions.size()); 
    int mission_end_location = Rand(0,missions.size()); 
    if(mission_start_location == mission_end_location) 
    { 
     if(!mission_start_location) 
      ++mission_end_location; 
     else 
      --mission_end_location; 
    } 
    ... 

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

Выполнение Rand(0,missions.size()); приведет к вам в опасности прочитав за торможение вашего вектора. Помните, что индексы std::vector основаны на 0, и когда Rand(0,missions.size()); происходит, чтобы вернуть значение, равное missions.size(), тогда вы будете считывать элемент один за концом вектора. Вы должны сделать

Rand(0,missions.size() - 1); 
//      ^^^ THIS 

вместо этого.

Еще один находится в этой части кода

if(mission_start_location == mission_end_location) 
    { 
     if(!mission_start_location) 
      ++mission_end_location; 
     else 
      --mission_end_location; 
    } 

Doing ++mission_end_location; и --mission_end_location; ставит вас, опять же, риск чтения не только после конца вашего вектора, но и перед его начальным элементом ,

один правильный способ делать то, что вы пытаетесь сделать, это

if(missions.size() > 1) 
{ 
    int mission_start_location; 
    int mission_end_location; 

    while(true) { 
     mission_start_location = Rand(0,missions.size()); 
     mission_end_location = Rand(0,missions.size()); 

     if(mission_start_location == mission_end_location) continue; 
     else break; 
    } 

    //do mission 
} 

Этот код петли до mission_start_location и mission_end_location не отличаются.

+0

это возможный бесконечный цикл (хотя это очень маловероятно, но все же возможно, и я хочу этого избежать), к счастью, мой ранд - это верхняя эксклюзивная привязка (min + rand()% (max-min) if (max-min)! = 0). код, чтобы прояснить мое мышление. – Gizmo

1

Самое простое решение:

int mission_start_location = Rand(0,missions.size()); 
int mission_end_location; 
do { 
    mission_end_location = Rand(0,missions.size()); 
} while (mission_end_location == mission_start_location); 

Я не думаю, что есть более C++ способ сделать это. Вы можете случайным образом перетасовать контейнер миссий, используя стандартную библиотечную функцию shuffle, но это будет иметь побочный эффект случайного перетасовки контейнера, и это слишком сложно для получения двух случайных элементов.

Если вы действительно хотите сделать это ровно два звонки на генератор случайных чисел:

int mission_start_location = Rand(0, missions.size()); 
int mission_end_location = Rand(0, missions.size() - 1); 
if (mission_end_location >= mission_start_location) ++mission_end_location; 

Я не знаю, что Rand функции вы используете, но вы можете захотеть взглянуть на функции реализованный в <random>

+0

это именно то, что я хочу предотвратить (снова этот комментарий) - возможно, но чрезвычайно маловероятный бесконечный цикл – Gizmo

+0

@ Гизмо: Хорошо, я предоставляю альтернативу. Но, как все говорили, беспокоиться о «бесконечный» цикл в этом сценарии - пустая трата времени. Альтернативное решение, которое я предоставляю, отлично подходит для двух элементов, но его сложно обобщить, в то время как цикл прост. – rici

Смежные вопросы