В большинстве ответов предлагается перетасовать начальный контейнер.Если вы не хотите, чтобы он был изменен, вы все равно можете использовать этот подход, но сначала вам нужно скопировать контейнер. Решение @ pmr (что приятно, потому что он превращает его в функцию) станет:
template <typename InputIterator, typename Size, typename OutputIterator>
void take_random_n(InputIterator first, InputIterator last,
Size n, OutputIterator result)
{
typedef typename std::iterator_traits<InputIterator>::value_type value_type;
std::vector<value_type> shufflingVec(first, last);
std::random_shuffle(shufflingVec.begin(), shufflingVec.end());
std::copy(shufflingVec.begin(), shufflingVec.begin() + n, result);
}
Однако копирование всего контейнера может быть довольно дорогим, если элементы содержаттяжелы и требуют времени для копирования.В этом случае вам лучше будет перетасовать список индексов:
template <typename InputIterator, typename Size, typename OutputIterator>
void take_random_n(InputIterator first, InputIterator last,
Size n, OutputIterator result)
{
typedef typename
std::iterator_traits<InputIterator>::value_type value_type;
typedef typename
std::iterator_traits<InputIterator>::difference_type difference_type;
difference_type size = std::distance(first, last);
std::vector<value_type> indexesVec(
boost::counting_iterator<size_t>(0),
boost::counting_iterator<size_t>(size));
// counting_iterator generates incrementing numbers. Easy to implement if you
// can't use Boost
std::random_shuffle(indexesVec.begin(), indexesVec.end());
for (Size i = 0 ; i < n ; ++i)
{
*result++ = *std::advance(first, indexesVec[i]);
}
}
// Disclaimer: I have not tested the code above!
Вы заметите, что последнее решение будет работать очень по-разному в зависимости от типа используемых итераторов: с итераторами с произвольным доступом (как указатели или vector<T>::iterator
), все будет в порядке, но с другими типами итераторов использование std::distance
и многочисленные вызовы std::advance
могут вызвать значительные издержки.