如何生成唯一的、可预测的、可重复的、非连续的字母数字标识符?

How can I generate a unique, predictable, repeatable, non sequential alphanumeric identifier?

我必须生成由四个字母数字字符组成的标识符,例如B41F.

我有以下要求:

  1. 每个标识符必须是唯一的(没有用于查找现有标识符的中心位置)
  2. 标识符不能明显连续(例如 1A01、1A02)
  3. 一定是可以预见的
  4. 仅使用标识符索引必须是可重复的(在两个不同的环境中,生成的第 N 个具有索引 N 的标识符必须相同)

该问题对任何语言都是通用的。我的实现将在 dart 中完成。

我认为这可以通过 PRNG 和一些 LUT 来完成,但是如果不重放整个序列,我找不到任何符合要求 4) 的实现或伪代码。此外,一些 PRNG 实现有一个随机组件,不能保证在库更新时可重复。

我怎样才能做到这一点?我正在寻找伪代码、代码或提示。

当标识符必须唯一时,您不应使用 PRNG。 RNG 不保证唯一性。有些人可能需要很长时间才能重复,但这是他们的全部 bit-range,将其减少到较小的数量可能会导致更早的冲突。

您的标识符实际上只是以 36 为基数的数字,因此您需要 shuffle(index).toRadixString(36) 之类的东西来生成它。

棘手的一点是 shuffle 函数,它必须是数字 0..36^4-1 的排列,看起来是随机的 (non-sequential),但可以计算 (有效?)对于任何输入。

因为 36^4 不是 2 的幂,所以大多数简单的 bit-shuffles 可能都行不通。

如果你只能接受 32^4 个数字 (2^20 ~ 1M) 可能会更容易。 然后,您还可以选择从结果中删除 OI01,这可能更易于阅读。

在那种情况下,我会做一些原始的事情(根本不是加密安全的),比如:

// Represent 20-bit numbers
String represent(int index) {
  RangeError.checkValueInInterval(index, 0, 0xFFFFF, "index");
  var digits = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
  return "${digits[(index >> 15) & 31]}${digits[(index >> 10) & 31]}"
       "${digits[(index >> 5) & 31]}${digits[index & 31]}";
}

// Completely naive number shuffler for 20-bit numbers.
// All numbers made up on the spot.
int shuffle(int index) {
  RangeError.checkValueInInterval(index, 0, 0xFFFFF, "index");
  index ^= 0x35712;
  index ^= index << 15;
  index ^= index << 4;
  index ^= index << 12;
  index ^= index << 7;
  index ^= index << 17;
  return index & 0xFFFFF; // 20 bit only.
}

如果您真的想要使用完整的 36^4 范围,我可能会做类似洗牌的事情,但是在 base-six 算术中。也许:

String represent(int index) =>   
    RangeError.checkValueInInterval(index, 0, 1679615, "index")
        .toRadixString(36).toUpperCase();

int shuffle(int index) {
  RangeError.checkValueInInterval(index, 0, 1679615, "index");
  const seed = [1, 4, 2, 5, 0, 3, 1, 4]; // seed.
  var digits = List<int>.filled(8, 0);
  for (var i = 0; i < 8; i++) {
    digits[i] = index.remainder(6);
    index = index ~/ 6;
  }

  void shiftAdd(List<int> source, int shift, int times) {
    for (var n = digits.length - 1 - shift; n >= 0; n--) {
      digits[shift + n] = (digits[shift + n] + source[n] * times).remainder(6);
    }
  }

  shiftAdd(seed, 0, 1);
  shiftAdd(digits, 3, 2);
  shiftAdd(digits, 5, 1);
  shiftAdd(digits, 2, 5);
  var result = 0;
  for (var i = digits.length - 1; i >= 0; i--) {
    result = result * 6 + digits[i];
  }
  return result;
}

同样,这是我当场编造的东西,它“洗牌”,但不保证结果的属性,除此之外它们看起来顺序。