std :: shuffle不能用std :: list编译

时间:2015-01-19 21:38:05

标签: c++ list std shuffle

我正在试图改组一些生成的元素列表。这是代码:

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'

有人有任何想法吗?

4 个答案:

答案 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节省更多空间。