从 JavaScript 中已知的一组潜在 ID 创建唯一字符串 ID/键的快速方法
Posted
技术标签:
【中文标题】从 JavaScript 中已知的一组潜在 ID 创建唯一字符串 ID/键的快速方法【英文标题】:Fast way to create a unique string ID/key from a known set of potential IDs in JavaScript 【发布时间】:2019-06-30 03:52:41 【问题描述】:假设您想要一组 1 到 2 位的十六进制数字,即 256 个数字。只需使用一个小集合即可解决问题,但它适用于任何大小的字符串。
所以在这种情况下,您有一个 潜在 N
或 256 个数字。您将为遇到的每条新数据记录“生成”一个新 ID。所以它开始并随机给你af
,然后是1d
,然后是8a
,等等。
最简单的方法是简单地按顺序生成所有数字,然后将它们洗牌,然后从集合中弹出。当您只有 256 个数字时,这可以正常工作。但是,如果您有数百万或数十亿个数字,这是不切实际的,因为您可能有很多生成的 ID 长时间没有使用。我想避免这种情况。
所以我的问题是创建这样的唯一键字符串的最快方法是什么,无需提前生成所有字符串,也无需按顺序递增 1 或诸如此类。也就是说,密钥应该看起来是随机的。
我可以想象的一种方法是使用 trie 存储已使用/生成的值。然后,当您要获得一个新值时,您会生成一个随机值,然后检查 trie 以查看它是否已被使用。我不知道如何判断它的效率如何,但是一旦你开始用完 ID 并且下降到集合中的最后几个 ID,它的性能似乎会非常糟糕。您会生成大量已经生成的 ID,并为每个 ID 遍历 trie,因此会很慢。
我想知道是否有更有效的方法来执行此操作,而无需提前生成它们。此外,数据记录不会用于计算 ID,因为记录可能非常庞大和复杂。
也许有一种方法可以一次随机遍历(并生成)一个 trie,并以这种方式生成 ID,因为您最终会在 trie 中的一个唯一随机位置结束。大概是这样的,我不知道。
另外,我对散列并不熟练,所以我不知道是否有任何好的方法。
【问题讨论】:
如果每个输入数据都是唯一的,您可以对输入使用哈希函数,并将其转换为数字。我想知道是否有一些不太复杂的数学函数可以在每次调用fn(0)
、fn(1)
等时生成从 0 到 N 的唯一值
输入数据不会用于生成 ID,因为它们可能是非常大和复杂的对象。
请去掉像“最快的方式”这样的主观措辞。在担心加快速度之前先担心解决问题
我知道如何以缓慢的方式解决问题,如前所述,因此是最快的方式。
如果你真的只使用了一个普通的计数器,比如1
、2
、3
等,然后将结果作为 ID 进行哈希处理,该怎么办?这样,ID 将是唯一的,并且仅按需生成,但它们在脚本之外没有任何明显的含义或顺序(我猜如果您需要数字而不是字符串,哈希可以转换回数字)
【参考方案1】:
我认为混合功能是您想要的。它将在您的输入中移动位以产生相同长度的输出。它是可逆的,因此每个输入对应一个唯一的输出。
由于您希望输入数据不参与 id 生成,因此您需要一个代理 id。您可以为每条记录分配一个递增的 id,并使用 mix 函数来打乱 id。
你会得到类似的东西:
记录 A =>id == 1
=> mixed id == 0x7ed55d16
记录 B => id == 2
=> mixed id == 0xc761c23c
等
请看这里以获得一些灵感:
https://crypto.stackexchange.com/questions/12145/need-32-bit-mixing-function-that-has-perfect-avalanche-between-octets https://gist.github.com/badboy/6267743【讨论】:
OP 在 cmets 中说:The input data wouldn't be used to generate the ID, as they could be extremely large and complicated objects
这个问题是一个移动的目标!我稍微更改了答案以适应该要求。
想知道您是否可以推荐一个实现,这些似乎都相当复杂,我真的不知道如何评估它们的使用情况。
在我的示例中,我使用了 2 长度的十六进制值,例如 8a
,但您的 0x7ed55d16
值非常大。看起来链接的混合函数都产生非常大的值,所以这是不正确的:/ 值应该是输入的大小,所以 256 或更小。
@LancePollard 是哪一个?你说你想处理“数百万或数十亿的数字”。在我花更多时间之前,请先说明你想要什么。【参考方案2】:
我认为应该在速度、灵活性和效率之间进行权衡。
一个有伪随机生成器会给你均匀分布的密钥,并且生成速度相当快。但是检查现有 id 会很慢。您可以使用布隆过滤器(节省内存)或尝试,但正如您所说的那样,您应该增加空间。
另一种选择是使用Gray code,它将生成每个密钥(但不是随机顺序)。您需要跟踪上次发布的代码。
【讨论】:
【参考方案3】:我不确定它的性能如何,但我的想法是使用 object
或 Map
和 Math.random()
let obj =
function generateRandomId()
let id = Math.abs( 0.5 - Math.random()) * 1000
if(obj[id])
generateRandomId()
else
obj[id] = true
return id
console.log(generateRandomId())
console.log(generateRandomId())
console.log(generateRandomId())
console.log(generateRandomId())
但如果你对使用模块没问题,我发现这个模块最有用
uuid 这会生成 RFC4122 UUIDS。
【讨论】:
99.99% 安全。 100% 如果包含 Date.now();避免重复ID【参考方案4】:我正在考虑这样的事情:
var trie = buildTrie()
var id1 = genId(trie)
var id2 = genId(trie)
console.log(id1,id2)
function buildTrie()
var trie = buildNode(0)
return trie
function buildNode(level)
if (level == 7) // 8 bits
var node =
available: true,
leaf: true
return node
else
var a = buildNode(level + 1)
var b = buildNode(level + 1)
var node =
availableLeft: true,
availableRight: true,
left: a,
right: b
a.parent = node
b.parent = node
return node
function genId(node)
var bytes = []
step(node, bytes)
var id = parseInt(bytes.join(''), 2).toString(16)
return id
function step(node, bytes)
if (node.leaf)
node.available = false
var c = node
var p = c.parent
while (p)
if (p.left == c)
p.availableLeft = false
else if (p.right == c)
p.availableRight = false
if (!p.availableLeft && !p.availableRight)
c = p
p = p.parent
else
p = false
var randomDirection = Math.random() >= 0.5
if (randomDirection)
if (node.availableLeft)
bytes.push(0)
step(node.left, bytes)
else if (node.availableRight)
bytes.push(1)
step(node.right, bytes)
else
if (node.availableRight)
bytes.push(1)
step(node.right, bytes)
else if (node.availableLeft)
bytes.push(0)
step(node.left, bytes)
也许有更好的方法。
【讨论】:
这是一个二叉树,所以你有2^nbLevels + 1 - 1
包含几个字段的节点。每个节点也在buildTrie()
中预先构建。这将很快为“数百万或数十亿个数字”占用大量内存。
@bernie 想知道您是否可以推荐一些空间效率更高的东西 (***.com/questions/245878/…)。
我在您的问题中看不到任何 要求 ids 是真正随机分配的。这是一个要求,还是您只是想要每个数据记录的非显而易见的标识符?如果不需要真正的随机性,它不会比我为每条记录存储一个数字的答案更紧凑。否则,使用的随机 id 需要存储在某个地方(哈希表、trie、数组等)
谢谢你说得通。不,我只是不想增加 ID。【参考方案5】:
我假设您可以生成顺序 ID;也就是说,您有一种可靠的方法可以准确地知道迄今为止已经生成了多少个 ID。然后,使用任何合理快速的加密算法对该计数进行加密就足够了。
加密将以二进制数的形式进行,大多数算法的加密结果大小相同,也是二进制的。如果需要,您可以对结果进行 base-64 或十六进制编码,使其更容易用作字符串。
由于加密必须是双射(即一对一映射)才能进行解密,因此每次都会产生不同的结果,直到总 ID 计数溢出。如果它是一个合理的加密函数,那么结果将显得随机(否则密码将易受攻击)。
【讨论】:
这与我前一天在回答中概述的过程完全相同,但使用“加密”作为混合功能...***.com/a/54549632/1030527 @bernie:我想这是真的;我并没有真正仔细阅读您的答案。这些事情发生了;我经常重复我的答案,有时是几年后。我想建议使用加密的好处是大多数程序员都知道如何找到一个库来做到这一点,而“混合功能”听起来像是必须研究和实现的东西。不过,显然这两个答案都不是 OP 想要的,所以我想还有其他一些未指定的约束。以上是关于从 JavaScript 中已知的一组潜在 ID 创建唯一字符串 ID/键的快速方法的主要内容,如果未能解决你的问题,请参考以下文章