将字符串转换为随机但可重复的均匀概率。

12
我该如何将字符串(例如用户ID加盐)转换为看似随机但实际上是确定性可重复的均匀概率,范围为[0.0, 1.0)?这意味着输出值≥0.0且<1.0。输出分布必须是均匀的,无论输入分布如何。例如,如果输入字符串为'a3b2Foobar',则输出概率可以重复为0.40341504。
跨语言和跨平台的算法可重现性是可取的。除非有更好的方法,否则我倾向于使用哈希函数。以下是我的代码:
>>> in_str = 'a3b2Foobar'
>>> (int(hashlib.sha256(in_str.encode()).hexdigest(), 16) % 1e8) / 1e8
0.40341504

我正在使用最新稳定版的Python 3。请注意,这个问题类似于但并不完全相同于相关问题convert an integer to a random but deterministically repeatable choice

实际上,你的问题与“将整数转换为随机但可重复选择”没有什么不同 - 一旦通过哈希将字符串转换为整数等效值,它就是完全相同的问题。 - Chris Johnson
@ChrisJohnson 好的,但是答案有实质性的不同。一个使用模数,另一个则没有。话虽如此,我认为在另一个答案中完全可以避免使用模数——而是通过将哈希值线性缩放到可用选择的数量来实现。 - Asclepius
1个回答

22

使用哈希

密码哈希值假定是 [0, MAX_HASH] 范围内的均匀分布整数,因此可以通过将其除以 MAX_HASH + 1 来缩放到 [0,1) 范围内的浮点数。

import hashlib

Hash = hashlib.sha512
MAX_HASH_PLUS_ONE = 2**(Hash().digest_size * 8)

def str_to_probability(in_str):
    """Return a reproducible uniformly random float in the interval [0, 1) for the given string."""
    seed = in_str.encode()
    hash_digest = Hash(seed).digest()
    hash_int = int.from_bytes(hash_digest, 'big')  # Uses explicit byteorder for system-agnostic reproducibility
    return hash_int / MAX_HASH_PLUS_ONE  # Float division

>>> str_to_probability('a3b2Foobar')
0.3659629991207491

这里有一个真实的应用示例

注意:

  • 不能使用内建的hash方法,因为它可能会保留输入的分布,例如hash(123)。或者在Python重新启动时,它可以返回不同的值,例如hash('123')
  • 使用取模运算并不必要,因为浮点数除法已足够。

使用随机数

random 模块可以使用 in_str 作为其种子,同时解决了线程安全性和连续性方面的问题。

采用此方法时,不仅跨语言的可重复性是一个问题,而且Python的多个未来版本之间的可重复性也可能成为一个问题。因此,不建议使用这种方法。

import random

def str_to_probability(in_str):
    """Return a reproducible uniformly random float in the interval [0, 1) for the given seed."""
    return random.Random(in_str).random()

>>> str_to_probability('a3b2Foobar')
0.4662507245848473

3
我同意hashlib解决方案。尤其是因为SHA512将在多个平台上实现。SHA用于加密,因此它是最接近随机但可重复的内容。虽然您可以研究其他加密方案,但最重要的是,您永远不应该自己实现。 - VoNWooDSoN
1
本答案假设使用Python 3。在Python 2中,您需要将其中一个输入转换为float才能获得浮点除法。 - SpliFF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接