Как обеспечить, чтобы два разных вектора перетасовывались в одном и том же порядке на С++?

У меня есть два вектора:

vector1 = [1 2 3 4 5 6 7 8 9]

vector2 = [1 2 3 4 5 6 7 8 9]

Я хочу убедиться, что при перетасовке с использованием random_shuffle их следует перетасовать в том же порядке. Например:

Результат после перетасовки должен выглядеть следующим образом:

vector1 = [1 9 3 4 2 7 8 5 6]

vector2 = [1 9 3 4 2 7 8 5 6]

Но я получаю вывод вроде:

vector1 = [5 1 7 4 2 3 9 8 6]

vector2 = [3 4 1 9 8 2 5 7 6]

Вот мой код:

int main () 
{
  std::srand ( unsigned ( std::time(0) ) );
  std::vector<int> vector1, vector2;

  // set some values:
  for (int i=1; i<10; ++i)
  {
    vector1.push_back(i);
    vector2.push_back(i);
  }

  // using built-in random generator:
  std::random_shuffle ( vector1.begin(), vector1.end() );
  std::random_shuffle ( vector2.begin(), vector2.end() );

  // print out content:
  std::cout << "vector1 contains:";
  for ( std::vector<int>::iterator it1 = vector1.begin(); it1 != vector1.end(); ++it1 )
    std::cout << ' ' << *it1;

  std::cout << '\n';
  std::cout << '\n';

  std::cout << "vector2 contains:";
  for ( std::vector<int>::iterator it2 = vector2.begin(); it2 != vector2.end(); ++it2 )
    std::cout << ' ' << *it2;

  std::cout << '\n';
  std::cout << '\n';

  return 0;
}

EDIT Это пример, который я пытался реализовать. На практике у меня есть один вектор изображений и один вектор соответствующих меток. Мне нужно, чтобы их тасовали одинаково. Может кто-нибудь помочь... Большое спасибо!

Ответы

Ответ 1

Вместо того, чтобы перетасовывать сами векторы, перетащите вектор индексов в другие векторы. Поскольку вы будете использовать те же индексы для обоих, они гарантированно будут в одном порядке.

std::vector<int> indexes;
indexes.reserve(vector1.size());
for (int i = 0; i < vector1.size(); ++i)
    indexes.push_back(i);
std::random_shuffle(indexes.begin(), indexes.end());

std::cout << "vector1 contains:";
for ( std::vector<int>::iterator it1 = indexes.begin(); it1 != indexes.end(); ++it1 )
    std::cout << ' ' << vector1[*it1];

Ответ 2

Убедитесь, что вы используете одно и то же семя для обоих вызовов: random_shuffle():

auto seed = unsigned ( std::time(0) );

// ...

std::srand ( seed );
std::random_shuffle ( vector1.begin(), vector1.end() );

std::srand ( seed );
std::random_shuffle ( vector2.begin(), vector2.end() );

Обратите внимание, однако, что стандарт не указывает, что random_shuffle() должен использовать функцию rand() для генерации случайной перестановки - это определение реализации. Поэтому srand() не повлияет на результат random_shuffle() на реализации, которые не используют rand().

Параграф 25.3.12/4 стандарта С++ 11 на random_shuffle() указывает:

Примечания. В той мере, в которой реализация этих функций использует случайные числа, реализация должна использовать следующие источники случайности:

Исходный источник случайных чисел для первой формы функции определяется реализацией. Реализация может использовать функцию rand из стандартной библиотеки C. [...]

Поэтому, если вы хотите убедиться, что вы пишете переносимый код, используйте версию random_shuffle(), которая принимает генератор случайных чисел в качестве третьего аргумента, так что вы имеете контроль над посевом.

Ответ 3

Как показали другие, повторное посев с тем же семенем позволит вам повторить один и тот же случайный перехват несколько раз. Однако, если вы можете использовать С++ 11, я бы рекомендовал реализовать это без использования srand() и random_shuffle(); вместо этого вы должны использовать библиотеку <random> с std::shuffle.

Во-первых, если возможно rand следует избегать. Помимо того факта, что он обычно не очень хороший pRNG, он также имеет проблемы с безопасностью потоков из-за общего состояния. Библиотека <random> исправляет обе эти проблемы, предоставляя ясному контролю программиста над состоянием pRNG и предоставляя несколько опций с гарантированными характеристиками производительности, размера и качества.

Во-вторых, random_shuffle на самом деле не используется для использования rand, поэтому теоретически законно для повторного использования с использованием srand не иметь желаемого эффекта. Чтобы получить гарантированные результаты с помощью random_shuffle, вам нужно написать собственный генератор. Перемещение на shuffle исправляет это, поскольку вы можете напрямую использовать стандартные двигатели.

#include <algorithm> // shuffle, copy
#include <iostream>  // cout
#include <iterator>  // begin, end, ostream_iterator
#include <numeric>   // iota
#include <random>    // default_random_engine, random_device
#include <vector>    // vector

int main() {
  std::vector<int> v1(10);
  std::iota(begin(v1), end(v1), 1);
  auto v2 = v1;

  std::random_device r;
  std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};

  // create two random engines with the same state
  std::mt19937 eng1(seed);
  auto eng2 = eng1;

  std::shuffle(begin(v1), end(v1), eng1);
  std::shuffle(begin(v2), end(v2), eng2);

  std::copy(begin(v1), end(v1), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\n\n";
  std::copy(begin(v2), end(v2), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\n\n";
}

Ответ 4

Разделите генератор псевдослучайных чисел с воспроизводимым значением перед каждым перетасовкой.

std::srand ( 42 );
std::random_shuffle ( vector1.begin(), vector1.end() );
std::srand ( 42 );
std::random_shuffle ( vector2.begin(), vector2.end() );

Ответ 5

Вы можете создать итератор с произвольным доступом, который, если его разыменовывает, возвращает std:: tuple в ссылки элементов соответствующих векторов. Поэтому вы можете перетасовать их на место. Или вы смотрите ускоренную версию. Поэтому он должен выглядеть примерно так:

std::random_shuffle(
  boost::make_zip_iterator(
    boost::make_tuple(vector1.begin(), vector2.begin())
  ),
  boost::make_zip_iterator(
    boost::make_tuple(vector1.end(), vector2.end()
  ),

);

Это перемещает ваши данные на место, работает с более чем двумя векторами и самодокументируется, если вы знаете, что делает make_zip_iterator. Конечно, он должен быть быстрее, чем перетасовать два раза или использовать третий вектор.

Ответ 6

Если оба должны иметь одинаковый порядок, почему они разделены векторы? Логическим решением было бы что-то вроде:

struct ImageData
{
    Image myImage;
    std::string myLabel;
    //  ...
};

Затем у вас есть один вектор ImageData, который вы перетасовываете.

Ответ 7

К сожалению, если мы используем srand, мы меняем внутреннее семенное значение. Я имею в виду, что следующие случайные числа будут предопределены. И, первое решение:

std::srand ( 42 );
std::random_shuffle ( vector1.begin(), vector1.end() );
std::srand ( 42 );
std::random_shuffle ( vector2.begin(), vector2.end() );
std::srand ( unsigned ( std::time(0) ) );
// Post-code.

Чтобы сохранить rand для пост-кода.

Второе решение - это решение Mark Ransom - он вообще не вызывает std:: srand (и, как мне кажется, он имеет более высокую производительность).

Ответ 8

Почему бы вам не написать свой собственный тасовка:

for( size_t i = 0 ; i < numitems; ++i )
{
    size_t next = random() % numitems ;
    swap( v1[i], v1[next] );
    swap( v2[i], v2[next] );
}