【问题标题】:Any way to use STL algorithm to shuffle two vectors simultaneously in C++?有什么方法可以使用 STL 算法在 C++ 中同时打乱两个向量?
【发布时间】:2021-01-31 03:59:42
【问题描述】:

我正在将 Java 程序移植到 C++。我有一段代码可以在 Java 中同时打乱两个数组,这产生了一种返回打乱数组索引的方法,因此可以用来相应地重新定位另一个数组(相同长度)。在 C++ 中,我使用以下算法对向量进行洗牌

#include <vector>
#include <algorithm>
#include <random>
#include <iostream>

using namespace std;
int main(void) {

    vector<int> A, B;
 
    for (int n=0; n<10; n++) {
        A.push_back(n);
        B.push_back(n);
    }
    
    std::random_device rd;
    std::mt19937 gen;
    std::uniform_int_distribution<> rnd(0, A.size()-1);
    for (int n=0; n<A.size(); n++) {
        int m = rnd(gen);
        std::swap(A[n], A[m]);
        std::swap(B[n], B[m]);
    }
    
    for (auto it: A) cout << it << " ";
    cout << endl;
    for (auto it: B) cout << it << " ";
    cout << endl;
    return 0;
}

它有效。但我想知道是否有任何 STL 算法可以同时 shuffle 两个或多个容器。

【问题讨论】:

  • 你是否需要真正洗牌两个容器,或者你可以洗牌,比如说,一个初始化为{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}的数组? (要获得洗牌后的Aith 元素,您可以使用A[shuffled[i]] 而不是A[i]。)我想这取决于如何访问数组。如果访问不是中心化的,这种“辅助洗牌”很容易突破遗忘。
  • 哦,还有一个要检查的细节:您展示了一对vectors。这可以用pairs 的vector 代替吗?
  • 你为什么不 std:: 洗牌?这将取代你的循环。第二件事,stl为什么要提供同时处理多个容器的算法呢?您期望获得什么样的优势?
  • @JaMiT,对一组索引进行洗牌并从洗牌后的索引中提取相应的条目是一种方法。但它需要额外的索引存储空间,我怀疑是否需要更多空间将数据复制回原位向量。改为 se vector 并从那里洗牌还有另一种方法,但是数据源作为两个向量给出,这也需要额外的操作和空间实际数据很大,额外的复制,粘贴和移动也可以减缓。我试过你的想法,但目前看来上面显示的循环方法更好。
  • @MuhammetAliAsan,使用 std::shuffle 不会以同样的方式洗牌两个容器,对吗?我是说 stl 是否应该提供它,我只是想知道 std::shuffle 是否有任何方式在 shuffle 之后提供索引或任何方式来实现类似 stl 的算法来工作。

标签: c++ random shuffle


【解决方案1】:

编辑:Armin 删除了他的答案,所以我的答案似乎断章取义。让我们展开

tl;dr 不,STL 中没有将相同的 shuffle 应用于多个容器的函数。 STL 相当大,但不能做所有事情。对算法的具体要求太多了。你问的不是那种常见的做法。

但是,没有什么能阻止您编写自己的算法。例如,您可以复制算法shown on std::shuffle on cppreference.com (third version) 并对其进行修改。

#include <iterator>

template<class URBG, class RandomIt1, class RandomIt2>
static void SameShuffleToMany(URBG&& g, RandomIt1 first1, RandomIt1 last1, RandomIt2 first2) {
    using diff_t = typename std::iterator_traits<RandomIt1>::difference_type;
    using distr_t = std::uniform_int_distribution<diff_t>;
    using param_t = typename distr_t::param_type;

    distr_t D;
    diff_t n = last1 - first1;
    for (diff_t i = n-1; i > 0; --i) {
        diff_t j = D(g, param_t(0, i));
        std::swap(first1[i], first1[j]);
        std::swap(first2[i], first2[j]);
    }
}

哎呀,你甚至可以使用可变参数模板参数来并行处理超过 2 个容器。

但是,正如 cmets 中所建议的,有多种解决方案可以解决此问题。每个解决方案都有自己的优点和缺点。我在下面做了一个速度台。但是,Armin 和 Jamit 的解决方案将需要额外的内存来存储新的索引和重新排序输出的目标。


旧答案

我做了一些比较基准代码

#include <benchmark/benchmark.h>
#include <vector>
#include <algorithm>
#include <random>
#include <iostream>

static constexpr auto N = 1000;

static std::vector<int> CreateVector() {
    std::vector<int> out;
    out.reserve(N);
    std::generate_n(back_inserter(out), N,
        [gen = std::mt19937(std::random_device{}())] () mutable { return gen();}
    );
    return out;
}

static void BM_Original(benchmark::State& state) {
    auto a = CreateVector();
    auto b = a; // elementwise copy

    for (auto _ : state) {
        std::random_device rd;
        std::mt19937 gen(std::random_device{}());
        std::uniform_int_distribution<> rnd(0, a.size()-1);
        for (int n = 0; n < a.size(); ++n) {
            int m = rnd(gen);
            std::swap(a[n], a[m]);
            std::swap(b[n], b[m]);
        }
    }

    if (!std::equal(cbegin(a), cend(a), cbegin(b)))
        std::cout << "Vectors are not equal!\n";
}
// Register the function as a benchmark
BENCHMARK(BM_Original);

static void BM_Jamit(benchmark::State& state) {
    auto a = CreateVector();
    auto b = a; // elementwise copy

    for (auto _ : state) {
        std::vector<int> idx(N);
        std::iota(begin(idx), end(idx), 0);
        std::mt19937 g(std::random_device{}());
        std::shuffle(begin(idx), end(idx), g);
        std::vector<int> aout; aout.reserve(N);
        std::transform(cbegin(idx), cend(idx), back_inserter(aout), [&](int i) { return a[i]; });
        a = std::move(aout);
        std::vector<int> bout; bout.reserve(N);
        std::transform(cbegin(idx), cend(idx), back_inserter(aout), [&](int i) { return b[i]; });
        b = std::move(bout);
    }

    if (!std::equal(cbegin(a), cend(a), cbegin(b)))
        std::cout << "Vectors are not equal!\n";
}
// Register the function as a benchmark
BENCHMARK(BM_Jamit);

static void BM_Armin(benchmark::State& state) {
    auto a = CreateVector();
    auto b = a; // elementwise copy

    for (auto _ : state) {
        const unsigned int seedValue = std::random_device()();
        std::mt19937 uniformRandomBitGenerator{};
        uniformRandomBitGenerator.seed(seedValue);
        std::shuffle(begin(a), end(a), uniformRandomBitGenerator);
        uniformRandomBitGenerator.seed(seedValue);
        std::shuffle(begin(b), end(b), uniformRandomBitGenerator);
    }

    if (!std::equal(cbegin(a), cend(a), cbegin(b)))
        std::cout << "Vectors are not equal!\n";
}
// Register the function as a benchmark
BENCHMARK(BM_Armin);


#include <iterator>

template<class URBG, class RandomIt1, class RandomIt2>
static void SameShuffleToMany(URBG&& g, RandomIt1 first1, RandomIt1 last1, RandomIt2 first2) {
    using diff_t = typename std::iterator_traits<RandomIt1>::difference_type;
    using distr_t = std::uniform_int_distribution<diff_t>;
    using param_t = typename distr_t::param_type;

    distr_t D;
    diff_t n = last1 - first1;
    for (diff_t i = n-1; i > 0; --i) {
        diff_t j = D(g, param_t(0, i));
        std::swap(first1[i], first1[j]);
        std::swap(first2[i], first2[j]);
    }
}

static void BM_Mine(benchmark::State& state) {
    auto a = CreateVector();
    auto b = a; // elementwise copy

    for (auto _ : state) {
        std::mt19937 g{std::random_device{}()};
        SameShuffleToMany(g, begin(a), end(a), begin(b));
    }

    if (!std::equal(cbegin(a), cend(a), cbegin(b)))
        std::cout << "Vectors are not equal!\n";
}
// Register the function as a benchmark
BENCHMARK(BM_Mine);

BENCHMARK_MAIN();

得到的 CPU 时间为:

  • 原始:108600 ns
  • Jamit:76000 ns
  • Armin:122000 ns
  • 我的:102000 ns

link to QuickBench让我知道这是否适合你。我不认为那里的链接共享与 GodBolt 一样好。

【讨论】:

  • OPs 原来的解决方案是错误的。不统一。
猜你喜欢
  • 2012-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-13
相关资源
最近更新 更多