我正在试图改组一些生成的元素列表。这是代码:
std::default_random_engine generator (10);
std::list<int> list(10);
int n = 0;
std::generate(list.begin(), list.end(), [&]{ return n++; });
std::shuffle(list.begin(), list.end(), generator);
它没有编译。以下是错误:
/include/c++/v1/algorithm:3059:34: Invalid operands to binary expression ('std::__1::__list_iterator<int, void *>' and 'std::__1::__list_iterator<int, void *>')
main.cpp:1:10: In file included from main.cpp:1:
/include/c++/v1/random:1641:10: In file included from /bin/../include/c++/v1/random:1641:
main.cpp:37:10: In instantiation of function template specialization 'std::__1::shuffle<std::__1::__list_iterator<int, void *>, std::__1::linear_congruential_engine<unsigned int, 48271, 0, 2147483647> &>' requested here
/include/c++/v1/iterator:622:1: Candidate template ignored: could not match 'reverse_iterator' against '__list_iterator'
/include/c++/v1/iterator:1017:1: Candidate template ignored: could not match 'move_iterator' against '__list_iterator'
/include/c++/v1/iterator:1369:1: Candidate template ignored: could not match '__wrap_iter' against '__list_iterator'
/include/c++/v1/string:486:11: Candidate template ignored: could not match 'fpos' against '__list_iterator'
有人有任何想法吗?
答案 0 :(得分:12)
std::list
不提供对其std::shuffle()
所需元素的随机访问权限。这就是std::shuffle()
的签名在其规范中的表现(C ++标准第25.3.12段):
template<class RandomAccessIterator, class UniformRandomNumberGenerator>
void shuffle(RandomAccessIterator first,
RandomAccessIterator last,
UniformRandomNumberGenerator&& g);
如果可以,请考虑使用std::vector
代替 - 顺便说一下,我们鼓励您将其用作C ++标准本身的默认顺序容器。
作为示例(live demo on Coliru):
int main()
{
std::default_random_engine generator(10);
std::vector<int> v(10);
std::iota(begin(v), end(v), 0);
std::shuffle(begin(v), end(v), generator);
for (auto x : v) { std::cout << x; }
}
std::iota()
算法只是您对std::generate
的特定用法的简单替代方法。
答案 1 :(得分:8)
std::shuffle
需要随机访问迭代器。 std::list
不提供这些内容。您需要一个不同的容器,例如std::vector
。
如果您确实需要std::list
,则可能需要在专用算法中实施混洗。但首先要确保你真的需要它。很多时候人们认为他们真正需要std::list
时需要std::vector
。
答案 2 :(得分:3)
[algorithms.general] / 2,声明shuffle
:
template<class RandomAccessIterator, class UniformRandomNumberGenerator> void shuffle(RandomAccessIterator first, RandomAccessIterator last, UniformRandomNumberGenerator&& rand);
[..]
如果算法的模板参数为
RandomAccessIterator
[..] 实际模板参数应满足随机访问迭代器(24.2.7)的要求。
显然std::list
仅提供双向迭代器。尝试使用一个提供随机访问迭代器的容器。
答案 3 :(得分:0)
AndyProwl正确地解释了为什么代码无法编译的原因,并指出使用std :: vector比使用std :: list更为合适。但是,juanchopanza有点吓人,他声称改组std :: list需要专用的算法。实际上,使用std :: shuffle可以很容易地通过引用的中间向量(和适当的话,使用move构造函数)对std :: shuffle进行混洗:
#include <numeric>
#include <random>
template <typename T, typename URBG>
void shuffle(std::list<T>& l, URBG&& urbg)
{
std::vector<std::reference_wrapper<const T>> v(l.begin(), l.end());
std::shuffle(v.begin(), v.end(), urbg);
std::list<T> shuffled;
for (auto &ref : v) shuffled.push_back(std::move(ref.get()));
l.swap(shuffled);
}
int main()
{
std::list<int> l(10);
std::iota(l.begin(), l.end(), 0);
shuffle(l, std::mt19937{ std::random_device{}() });
for (auto x : l) std::cout << x << " ";
}
实际上,我们甚至可以做得更好:由于可以在列表之间移动元素而无需使迭代器或引用无效,因此我们甚至可以随机播放既不可复制也不可移动的对象列表,并且(更重要的是)还可以确保改组保留对列表元素的引用。
template <typename T, typename URBG>
void shuffle(std::list<T>& l, URBG&& urbg)
{
std::vector<std::list<T>::const_iterator> v;
for (auto it = l.cbegin(); it != l.cend(); ++it) v.push_back(it);
std::shuffle(v.begin(), v.end(), urbg);
std::list<T> shuffled;
for (auto &it : v) shuffled.splice(shuffled.end(), l, it);
l.swap(shuffled);
}
当然,这两种解决方案的引用向量(或迭代器)都具有O(n)空间开销。如果这是一个问题,那么您可以实现一个更慢的O(n log n)时间算法,该算法仅使用O(1)空间,如here所述。尽管您仍然可以通过切换到std :: forward_list节省更多空间。