std::shuffle 不能使用 std::list 编译
std::shuffle doesn't compile with std::list
我正在尝试洗牌一些生成的元素列表。这是代码:
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'
有人知道吗?
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++ 标准本身鼓励您将其用作默认顺序容器。
举个例子(科利鲁的现场演示):
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
.
std::shuffle
需要随机访问迭代器。 std::list
不提供这些。您需要不同的容器,例如 std::vector
。
如果你真的需要std::list
,你可能需要在专用算法中实现洗牌。但首先确保你真的需要它。很多时候,人们认为他们需要std::list
,而他们真的需要std::vector
。
[algorithms.general]/2, shuffle
声明:
template<class RandomAccessIterator, class UniformRandomNumberGenerator> void shuffle(RandomAccessIterator first, RandomAccessIterator last, UniformRandomNumberGenerator&& rand);
[..]
如果算法的模板参数
RandomAccessIterator
[..] 实际的模板参数应满足随机访问迭代器 (24.2.7) 的要求。
显然,std::list
只提供双向迭代器。尝试改用提供随机访问迭代器的容器。
AndyProwl正确地解释了为什么代码无法编译,并指出使用std::vector通常比使用std::list更合适。然而,juanchopanza在他的主张中略微感到害怕,即洗牌一个std::list需要一个专门的算法。事实上,使用 std::shuffle 很容易打乱 std::list 的中间向量(如果适用,请使用 move 构造函数):
#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(1) 空间的较慢的 O(n log n) 时间算法,如此处所述。尽管无论如何,您可能会通过切换到std::forward_list来节省更多空间。
- 使用std::multimap迭代器创建std::list
- 使用"std::unordereded_map"映射到"std::list"对象
- 使用std::list创建循环链表
- 如果 KEY 是 std::list 或 std::vector 而不是值,那么 std::map 的默认行为是什么?
- 从嵌套循环中的 std::list 中删除将返回访问冲突
- 包含 std::list 的结构体的 C++ 初始化
- 在基于范围的 for 循环期间插入 std::list 的后面
- 循环挂起迭代的 std::擦除 on std::list
- 如何增加以前由新运算符分配的 C++ std::list 数组的大小?
- std::list 中的迭代器感知对象
- std::list 可以用于简单的无锁队列吗?
- 使用 std::list 存储顶点并使用 SFML 绘制它们
- C++11 基于范围的 for 循环,用于 std::list
- 向 std::list 添加新元素的复杂性
- 如何使用 std::list 模板的迭代器擦除
- 将元素添加到 std::list 在多线程中,无需 C++ 互斥锁
- 如何将文件的一部分读取到std::list缓冲区?
- 抽象和派生与std::list相结合
- std::list 的两个 insert() 方法签名之间的实现差异
- C++:我可以重用/移动 std::list 元素从中间到结尾吗?