【问题标题】:Sample 3 integers without replacement in C++在 C++ 中采样 3 个整数而不进行替换
【发布时间】:2020-11-20 17:44:45
【问题描述】:

我想在{0, 1, ..., n-1} 中抽取三个整数而不进行替换。

到目前为止,我是这样进行的:

#include <random>

/* constructs vector {0, 1, ..., n-1} --------------------------------------- */
template <class T>
std::vector<T> integers_n(T n) {
  std::vector<T> out(n);
  for(T i = 0; i < n; i++) {
    out[i] = i;
  }
  return out;
}

/* samples three integers among {0, 1, ..., n-1} ---------------------------- */
const std::vector<int> choose3(const int n,
                               std::default_random_engine& generator) {
  std::uniform_int_distribution<int> sampler1(0, n - 1);
  std::uniform_int_distribution<int> sampler2(0, n - 2);
  std::uniform_int_distribution<int> sampler3(0, n - 3);
  const int i1 = sampler1(generator);
  const int i2 = sampler2(generator);
  const int i3 = sampler3(generator);
  std::vector<int> elems = integers_n(n);
  elems.erase(elems.begin() + i1);
  const int j2 = elems[i2];
  elems.erase(elems.begin() + i2);
  const int j3 = elems[i3];
  return {i1, j2, j3};
}

这可行,但有更好的方法吗?

我想在一个循环中多次执行此采样。每次迭代重新定义采样器是否耗时?

由于技术原因,我仅限于 C++ 11。

【问题讨论】:

  • 我知道您仅限于 C++11,但是当您升级到 C++17 时,请参阅目标上的第二个答案。
  • 谁结束了这个问题?链接的问题对我的问题没有适当的答案。 shuffle 洗牌整个范围!请重新打开。
  • 您始终可以使用@ ping 问题的更近者。我不跟。除了你要采样 3 个值,而目标要采样 4 个之外,为什么你认为这不是一个骗局?
  • 被标记的副本将整个范围洗牌,从中提取前 N 个值(就像洗一副牌,然后取前 N 个)。当您只需要少量值时,它在计算上会比我建议的答案更昂贵。
  • @1201ProgramAlarm 目标可能有更好的答案这一事实并不意味着这不是重复的。如果它比其他解决方案更好,您可以在那里发布您的答案。据我所知,这两个问题都在问同一个问题。

标签: c++ c++11 random


【解决方案1】:

您可以在不分配任何额外内存的情况下从0, ..., n - 1 进行采样而无需替换; elem 是不需要的。关键是模拟Fisher-Yates Shuffle的几个步骤,如David Eisenstat's answer to my related question所示。

// std::array<int, 3> requires no heap allocation, so is a better choice.
std::array<int, 3> choose3(const int n,
                           std::default_random_engine& generator) {
    std::uniform_int_distribution<int> sampler1(0, n - 1);
    std::uniform_int_distribution<int> sampler2(0, n - 2);
    std::uniform_int_distribution<int> sampler3(0, n - 3);
    // Algorithm translated from https://stackoverflow.com/a/64359519/1896169
    int i1 = sampler1(generator);
    int i2 = sampler2(generator);
    int i3 = sampler3(generator);

    if (i3 == i2) i3 = n - 2;
    if (i3 == i1) i3 = n - 1;
    if (i2 == i1) i2 = n - 1;
    return {i1, i2, i3};
}

【讨论】:

  • 谢谢。很清楚这是一个统一抽样吗? (我做了一些模拟,看起来确实是统一的)。
  • @StéphaneLaurent 它与其他解决方案一样统一。 3 个i 值来自均匀分布。如果其中两个值会导致重复数字,则可能会更改它们,但该更改的效果与替换 elem 中的元素相同,就像我在回答中所做的那样。
  • 感谢@1201ProgramAlarm。请问,if(i2 == i1) { i2 = i1 == n - 2 ? n - 1 : n - 2; } 仅针对两个整数的情况是否正确?
  • @StéphaneLaurent 不,仅对于两个整数,i2 == i1 大小写不变。 i2/i1 检查不依赖于以后如何选择其他值。您可以在分配给i2 后立即检查i2 == i1
  • @1201ProgramAlarm 对不起,我不明白 :(。你能给我看看代码吗?你的意思是必须保留第三个 if 吗?
【解决方案2】:

您可以用最后一个元素替换从向量中选择的元素,而不是使用erase

elems[i1] = elems[n - 1];

然后对第二个做同样的事情:

elems[i2] = elems[n - 2];

构建三个uniform_int_distribution 的时间最短。构造elems 会消耗更多时间。

【讨论】:

  • 您可以将其视为仅选择 3 个元素的部分 Fisher-Yates Shuffle。
猜你喜欢
  • 1970-01-01
  • 2016-02-21
  • 1970-01-01
  • 2019-05-12
  • 1970-01-01
  • 1970-01-01
  • 2020-06-22
  • 2021-12-08
  • 2023-03-28
相关资源
最近更新 更多