如何在地图中随机混乱值?

时间:2013-04-17 19:49:32

标签: c++ algorithm map stl shuffle

我有一个std :: map,键和值都是整数。现在我想随机地改变地图,所以键随机指向不同的值。我尝试了random_shuffle但它没有编译。请注意,我并没有试图改变键,这对于地图来说没有意义。我试图随机化这些值。

我可以将值推送到矢量中,然后将其复制然后复制回来。还有更好的方法吗?

6 个答案:

答案 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>个索引。

为方便起见,您可以将mapvector封装在一个公开shuffle()成员函数的类中。这样的包装器还需要公开底层映射的基本查找/插入/擦除功能。

编辑:正如@tmyklebu在评论中指出的那样,维护(原始或智能)指向辅助数据的指针可能会受到迭代器失效的影响(例如,插入新内容时)最后的元素导致向量的容量调整大小)。使用索引而不是指针解决了“最后插入”问题。但是在编写包装器类时,您需要确保新键值对的插入永远不会导致辅助数据“插入中间”,因为这也会使索引无效。更强大的库解决方案是使用Boost.MultiIndex,它专门设计用于允许多种类型的数据结构视图。

答案 2 :(得分:0)

好吧,只使用地图我想到了: 为地图的每个单元格创建一个标志数组,随机生成两个整数s.t. 0&lt; = i,j&lt;地图大小;交换它们并将这些单元格标记为交换。为所有人迭代。
编辑:数组按地图大小分配,是一个本地数组。

答案 3 :(得分:0)

我怀疑......

但是......为什么不写一个有两个向量的快速类。一个有序的std::vector个键和std::random_shufflestd::vector个值?使用std::lower_bound查找密钥,并使用std::distancestd::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());
}