Random 当可能发生冲突且成本高昂时,如何在不重复的情况下生成随机值?

Random 当可能发生冲突且成本高昂时,如何在不重复的情况下生成随机值?,random,Random,我知道有很多这样的问题,但它们在一个关键方面似乎都不同:我的碰撞管理问题更具挑战性 我的示例空间是aaaaaaa格式的序列,其中a是字母[a-z]。这就是26^5=11881376个组合。(请注意,我的字号(5)和字母表大小(26)都很小。这是因为我需要合理的记忆单词。这意味着我可能需要在1200万个总可能性中分配大约100万个,这意味着冲突的可能性远远大于从2^32个可能的整数中选择100个。) 此外,我需要生成一个随机值,它不能与任何现有值冲突,但这些现有值是在很长一段时间内生成的,并存储在

我知道有很多这样的问题,但它们在一个关键方面似乎都不同:我的碰撞管理问题更具挑战性

我的示例空间是
aaaaaaa
格式的序列,其中a是字母[a-z]。这就是26^5=11881376个组合。(请注意,我的字号(5)和字母表大小(26)都很小。这是因为我需要合理的记忆单词。这意味着我可能需要在1200万个总可能性中分配大约100万个,这意味着冲突的可能性远远大于从2^32个可能的整数中选择100个。)

此外,我需要生成一个随机值,它不能与任何现有值冲突,但这些现有值是在很长一段时间内生成的,并存储在数据库中。换言之,我的内存中没有它们,无法进行简单的碰撞检查

大多数生成随机值而不重复的算法都涉及生成一个值,并仅测试它是否存在冲突,然后重复,直到没有冲突为止。但在这里,这个测试意味着一个数据库调用,这将非常昂贵,而且我的冲突率要高得多。所以我想我会有问题的


有更好的方法吗?

利用你的宇宙很小的事实:用所有1200万填充一个数组。洗牌数组,使其以随机顺序排列。用它们填充数据库表,并编制索引(即数据库行看起来像(1,“hgfyu”)、(2,“aipes”)、(3,“zdpgb”)等)

然后(在另一张表格中)记录你已经分发了多少,当你需要另一张时,只需分发“下一张”并增加你的数量


另一种可能是更多的数学运算,更少的存储空间:只需记录你已经分发了多少。然后,每当您需要一个新的随机数时,使用可复制的RNG以固定顺序(称为K)查找第n个随机数,然后返回按字典顺序排列的第K个代码。

通常您会使用哈希计数器;只需维护一个从0到11881375的计数器,并对其应用一些双射映射函数,以便它们以随机顺序生成

比如:

// map any value < 2**24 to another value < 2**24, with no duplicates
int hash24(int x) {
  x = x ^ (x >> 12);
  x = (x * 0x818d6b) & 0xffffff;
  x = x ^ (x >> 10);
  x = (x * 0x0fa653) & 0xffffff;
  x = x ^ (x >> 12);
  return x;
}

void next(char result[6]) {
  static int s = 0;    // keep this value somewhere persistent
  const int seq = 0x55aa55;    // change this to re-randomize
  int r;

  // find the next random value less than 26**5
  do {
    r = hash24(s ^ seq);
    s = s + 1;
  } while (result >= 11881376);

  // map integer to string of letters
  for (int i = 0; i < 5; ++i) {
    result[i] = 'a' + (r % 26);
    r /= 26;
  }
  result[5] = '\0';
}
//将任何<2**24的值映射到另一个<2**24的值,不要重复
inthash24(intx){
x=x^(x>>12);
x=(x*0x818d6b)&0xffffff;
x=x^(x>>10);
x=(x*0x0fa653)和0xffffff;
x=x^(x>>12);
返回x;
}
void next(字符结果[6]){
static int s=0;//将此值保留在某个持久的位置
const int seq=0x55aa55;//将此更改为重新随机化
INTR;
//查找下一个小于26**5的随机值
做{
r=24(序列号);
s=s+1;
}而(结果>=11881376);
//将整数映射到字母字符串
对于(int i=0;i<5;++i){
结果[i]=“a”+(r%26);
r/=26;
}
结果[5]='\0';
}
然而,似乎您有更多的约束,您没有提到。你可能想避免使用那些不会让人记忆深刻的单词,因为它们完全是胡言乱语(比如YouTube的URL),同时你可能想避免使用冒犯性的单词(不知道YouTube是否会过滤这些单词,但我没有看到过)

因为26可以被2整除,所以制作一个不依赖于拒绝超出范围的值的版本是相当简单的,但这是一件不必要的奇怪事情:

int hash26p5(int x) {
  int r[5] = { 5, 11, 17, 23, 27 };
  for (int i = 0; i < 5; ++i) {
    int r = ((x & 31) * r[i]) & 31;
    x = (x >> 5) + r * 371293;
  }
  return x;
}

void next(char result[6]) {
  static int s = 0;    // keep this value somewhere persistent
  int r hash26p5(s);
  s = s + 1;

  // map integer to string of letters
  for (int i = 0; i < 5; ++i) {
    result[i] = 'a' + (r % 26);
    r /= 26;
  }
  result[5] = '\0';
}
inthash26p5(intx){
int r[5]={5,11,17,23,27};
对于(int i=0;i<5;++i){
int r=((x&31)*r[i])&31;
x=(x>>5)+r*371293;
}
返回x;
}
void next(字符结果[6]){
static int s=0;//将此值保留在某个持久的位置
int r hash26p5(s);
s=s+1;
//将整数映射到字母字符串
对于(int i=0;i<5;++i){
结果[i]=“a”+(r%26);
r/=26;
}
结果[5]='\0';
}

事实上,因为只有两个主要因素,所以可以做更有趣的事情,但它们更深奥,如果你移动到更小的字母表或不同的字母数,它们就不相关了。

是否不可能预先生成所有值,然后随机弹出它们?从一些快速的数学,它似乎并不需要那么多的内存。大约50mb,取决于类型?这应该是可管理的。随机标识符识别什么样的资源?您是否需要难以猜测的标识符,或者仅仅是随机的,或者仅仅是顺序的?如果它们需要是随机的,那么这些标识符是唯一允许访问这些资源的东西吗?我理解你的第一个建议,但我并不完全遵循第二个建议。当N较大时,如何有效地生成第N个1?如何确保没有冲突,使得K也是某些i的第(N-i)个数?生成字典序列中的第K个数很简单--只需将字母视为以26为底的数字即可。通过选择正确的生成器(如LFSR或专用分组密码)可以避免冲突。就像我说的,大量的数学运算,但是可行。我知道如何索引单词,但是使用LFSR,你不需要运行所有N个步骤来生成第N个术语吗?当N变大时,这将变得昂贵。您将LFSR的状态保持为持久状态,因此您只需要为每次使用生成下一个状态(并可能进行一些拒绝采样)。