我有一个std :: map,键和值都是整数。现在我想随机地改变地图,所以键随机指向不同的值。我尝试了random_shuffle但它没有编译。请注意,我并没有试图改变键,这对于地图来说没有意义。我试图随机化这些值。
我可以将值推送到矢量中,然后将其复制然后复制回来。还有更好的方法吗?
答案 0 :(得分:3)
您可以推送vector
中的所有密钥,随机播放vector
并使用它来交换map
中的值。
以下是一个例子:
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <random>
#include <ctime>
using namespace std;
int myrandom (int i) { return std::rand()%i;}
int main ()
{
srand(time(0));
map<int,string> m;
vector<int> v;
for(int i=0; i<10; i++)
m.insert(pair<int,string>(i,("v"+to_string(i))));
for(auto i: m)
{
cout << i.first << ":" << i.second << endl;
v.push_back(i.first);
}
random_shuffle(v.begin(), v.end(),myrandom);
vector<int>::iterator it=v.begin();
cout << endl;
for(auto& i:m)
{
string ts=i.second;
i.second=m[*it];
m[*it]=ts;
it++;
}
for(auto i: m)
{
cout << i.first << ":" << i.second << endl;
}
return 0;
}
答案 1 :(得分:2)
您的提案的复杂性为O(N)
,(副本和随机播放都具有线性复杂性)似乎是最优的(查看较少的元素会将非随机性引入您的随机播放)。
如果你想反复洗牌你的数据,你可以维护一个<Key, size_t>
类型的地图(即众所周知的间接等级),该地图索引成std::vector<Value>
然后重复洗牌。这可以节省您所有的复制,以换取O(N)
空间开销。如果Value
类型本身很昂贵,那么您在执行重排的实际数据中有额外的vector<size_t>
个索引。
为方便起见,您可以将map
和vector
封装在一个公开shuffle()
成员函数的类中。这样的包装器还需要公开底层映射的基本查找/插入/擦除功能。
编辑:正如@tmyklebu在评论中指出的那样,维护(原始或智能)指向辅助数据的指针可能会受到迭代器失效的影响(例如,插入新内容时)最后的元素导致向量的容量调整大小)。使用索引而不是指针解决了“最后插入”问题。但是在编写包装器类时,您需要确保新键值对的插入永远不会导致辅助数据“插入中间”,因为这也会使索引无效。更强大的库解决方案是使用Boost.MultiIndex,它专门设计用于允许多种类型的数据结构视图。
答案 2 :(得分:0)
好吧,只使用地图我想到了:
为地图的每个单元格创建一个标志数组,随机生成两个整数s.t. 0&lt; = i,j&lt;地图大小;交换它们并将这些单元格标记为交换。为所有人迭代。
编辑:数组按地图大小分配,是一个本地数组。
答案 3 :(得分:0)
我怀疑......
但是......为什么不写一个有两个向量的快速类。一个有序的std::vector
个键和std::random_shuffle
个std::vector
个值?使用std::lower_bound
查找密钥,并使用std::distance
和std::advance
获取值。简单!
如果不深入思考,这应该与std::map
具有相似的复杂性,并且可能具有更好的参考局部性。
一些未经测试和未完成的代码可以帮助您入门。
template <class Key, class T>
class random_map
{
public:
T& at(Key const& key);
void shuffle();
private:
std::vector<Key> d_keys; // Hold the keys of the *map*; MUST be sorted.
std::vector<T> d_values;
}
template <class Key, class T>
T& random_map<Key, T>::at(Key const& key)
{
auto lb = std::lower_bound(d_keys.begin(), d_keys.end(), key);
if(key < *lb) {
throw std::out_of_range();
}
auto delta = std::difference(d_keys.begin(), lb);
auto it = std::advance(d_values.begin(), lb);
return *it;
}
template <class Key, class T>
void random_map<Key, T>::shuffle()
{
random_shuffle(d_keys.begin(), d_keys.end());
}
答案 4 :(得分:0)
如果您想对地图进行随机播放,可以为random_shuffle
实施自己的map
版本。解决方案仍然需要将密钥放入向量中,使用transform
:
typedef std::map<int, std::string> map_type;
map_type m;
m[10] = "hello";
m[20] = "world";
m[30] = "!";
std::vector<map_type::key_type> v(m.size());
std::transform(m.begin(), m.end(), v.begin(),
[](const map_type::value_type &x){
return x.first;
});
srand48(time(0));
auto n = m.size();
for (auto i = n-1; i > 0; --i) {
map_type::size_type r = drand48() * (i+1);
std::swap(m[v[i]], m[v[r]]);
}
我使用drand48()/srand48()
作为统一的伪随机数生成器,但您可以使用最适合您的任何内容。
或者,您可以随机播放v
,然后重建map
,例如:
std::random_shuffle(v.begin(), v.end());
map_type m2 = m;
int i = 0;
for (auto &x : m) {
x.second = m2[v[i++]];
}
但是,我想说明在地图上实施shuffle并不会过于繁琐。
答案 5 :(得分:0)
以下是使用C ++ 11的std::reference_wrapper
的解决方案。
首先,让我们制作一个版本的std :: random_shuffle,它可以随机播放。它是here对版本1 的一个小修改:使用get
方法获取引用的值。
template< class RandomIt >
void shuffleRefs( RandomIt first, RandomIt last ) {
typename std::iterator_traits<RandomIt>::difference_type i, n;
n = last - first;
for (i = n-1; i > 0; --i) {
using std::swap;
swap(first[i].get(), first[std::rand() % (i+1)].get());
}
}
现在很容易:
template <class MapType>
void shuffleMap(MapType &map) {
std::vector<std::reference_wrapper<typename MapType::mapped_type>> v;
for (auto &el : map) v.push_back(std::ref(el.second));
shuffleRefs(v.begin(), v.end());
}