生成随机函数(与随机数字不同)

3
我想创建一个函数,它接受一个字符串并返回0到1之间的数字。该函数应在给定相同字符串时始终返回相同的数字,但除此之外,结果应没有可辨别的模式。任何大量输入字符串的输出数字应遵循均匀分布。
此外,我需要生成多个这样的函数,例如对于字符串“abc”,函数A可能始终返回0.593927,而函数B始终返回0.0162524。我需要它快速(用于数值模拟),并具有合理的统计数据。
我正在使用Python,并将满足以下答案:“这是使用Python库轻松完成的方法”或“这是您可以实现的算法。”如果在Python中没有快速的方法,我会转而使用C语言。
我意识到以下两种方法都可以工作,但它们各自具有缺点,使我希望寻找更优雅的解决方案。
存储字典 我可以每次收到新字符串时计算一个新的随机数,并将其存储在字典中以便再次检索相同的字符串时使用。然而,我的应用程序可能会生成很多仅出现一次的字符串,这最终会导致必须在内存中存储非常大的字典。它还使得重复性更加困难,因为即使我使用相同的种子,如果以不同的顺序接收相同的字符串,我也会生成不同的函数。因此,为了这些原因,更好的方法是在计算随机数时始终保持“即兴表演”。
使用哈希函数 我可以仅对字符串调用哈希函数,然后将结果转换为数字。例如,通过将“种子”字符串附加到每个输入字符串中来解决生成多个函数的问题。但是,那么我将被困在尝试找到具有适当速度和统计数据的哈希函数中。Python内置的哈希很快,但实现依赖于具体实现,而且我不知道统计数据有多好,因为它不是为此类目的而设计的。另一方面,我可以使用诸如md5之类的安全哈希算法,它将具有良好的统计数据,但这对于我的应用程序来说太慢了。针对数据存储应用程序的哈希函数通常比MD5等密码学安全功能要快得多,但它们的设计目的是避免冲突,而不是产生均匀分布的输出,在所有情况下这些目标不一定相同。
哈希函数的进一步说明
为了说明避免冲突和产生均匀结果是不同的事情,请考虑以下示例,其中使用Python内置的哈希函数:
>>> hash("aaa") % 1000
340
>>> hash("aab") % 1000
343
>>> hash("aac") % 1000
342
>>> hash("aad") % 1000
337
>>> hash("aae") % 1000
336
>>> hash("aaf") % 1000
339
>>> hash("aag") % 1000
338
>>> hash("aah") % 1000
349
>>> hash("aai") % 1000
348
>>> hash("aaj") % 1000
351
>>> hash("aak") % 1000
350

上述输出中没有碰撞,但它们显然不是均匀分布的,因为它们都介于336和351之间,在第三位数字中也有明确的模式。我意识到通过执行(hash("aaa")/HASH_MAX)*1000(假设我可以计算出HASH_MAX的值)可能会得到更好的统计结果,但这应该有助于说明一个好的哈希函数的要求并不同于我所寻找的函数的要求。
关于问题的一些相关信息:
我不知道这个算法将需要处理哪些字符串,因为这些字符串将由模拟程序生成,但以下情况很可能成立:
1. 它们将具有非常受限制的字符集(可能只有4或5个不同的符号)。 2. 有许多唯一或罕见的字符串和少数非常常见的字符串,长度各异。 3. 字符串的长度没有上限,但短字符串很可能比长字符串更常见。我不会惊讶地看到长度超过100个字符的字符串,但我不能确定。其中许多只有一个到三个字符,因此对于短字符串来说,算法快速运行很重要。(但我想我可以为小于某个长度的字符串使用查找表。) 4. 通常,字符串将具有共同的大子字符串——通常两个字符串仅在开头或结尾附加一个单个字符。重要的是,当字符串相似时,算法不倾向于产生类似的输出值。

1
一定要使用哈希表。这些字符串的分布情况是什么? - John Dvorak
你实现的Python中的哈希都会比md5慢。你需要将其写入扩展模块中。也许rc4对你来说已经足够了? - John La Rooy
@JanDvorak 字符串的分布很难事先预测,因为它将取决于非常复杂的模拟动态。但是它很可能包含许多不同的字符串和少量非常常见的字符串,长度各异。 - N. Virgo
@gnibbler 我认为RC4是一对一加密,而不是哈希。 - Gene
1
@JanDvorak 其他相关的分布特征是 (i) 字符串可能具有相当受限制的字符集,以及 (ii) 字符串通常会彼此共享大量子字符串。 - N. Virgo
显示剩余7条评论
4个回答

3
使用一个好的随机数生成器,并使用该字符串对其进行种子化。

我很好奇你是如何使用字符串来生成随机数的 - 这可能是一个关键的因素。 - martineau
1
Python允许使用任何可哈希对象作为种子,因此我认为它会获取(内置的)哈希值,然后使用该值。 - N. Virgo
1
Python内置的哈希函数生成32位值,这可能会导致你所做的事情发生太多冲突。 - martineau
1
嗯,这是个好点子。我想我得使用另一个随机数生成器,因为我不认为有任何方法可以给Python内置的随机数生成器提供超过32位的种子(尽管从get_state函数的输出来看,它的内部状态似乎有很多)。如果我可以将多个32位值作为种子,我只需将字符串分成段并对其进行哈希处理即可。 - N. Virgo
抱歉打扰,但我刚在Python的_random C代码中发现它不仅限于32位:https://hg.python.org/cpython/file/2.7/Modules/_randommodule.c#l231 它可以接受“long”并将其分成32位块。 - MarSoft
显示剩余5条评论

1
在维基百科关于通用哈希的“字符串哈希”部分中有一个算法。 或者,您可以使用一些内置的哈希函数;每个随机函数在哈希之前将一个随机(但固定的)前缀添加到字符串中。

是的,我说过我的问题。避免碰撞的问题并不等同于产生均匀分布的输出。 - N. Virgo
非常抱歉 - 我之前误解了你的回答,因为我对你链接的维基百科文章只是匆匆浏览了一下。我认为这样的“通用哈希”算法正是我正在寻找的! - N. Virgo

1

Lookup3 被认为具有非常好的碰撞属性,这应该意味着结果的均匀分布,并且速度也很快。将其放入Python扩展应该很简单。

更一般地说,如果您找到了一个在最小化哈希表冲突方面做得很好并且具有所需速度属性的函数,则仅需要将32位或64位整数转换为浮点数即可。网络和其他地方有许多字符串哈希函数的来源。首先检查Knuth

补充

另外一个值得尝试的方法是首先使用像RC4(不安全,但仍足够接近伪随机)这样的快速1-1算法加密字符串,然后在密文上运行一个微不足道的哈希(h = h + a * c[i] + b)。 RC4密钥是唯一标识符。


我会查看那些链接,但我不确定一个擅长避免冲突的算法是否也会产生均匀分布的输出。或者说,一个在其中一方面完美的算法也将在另一方面完美,但是对于一个应用程序而言“足够好”的算法未必对另一个应用程序足够好。 - N. Virgo
好的,你似乎排除了加密哈希。这将是你所描述的“完美”,但速度太慢了。我想指出的是,在加密级别的统计纯度和必然存在问题的简单哈希之间有一个折中方案:就是像lookup3这样的哈希,它会尽力避免冲突,但仍然非常快速。 - Gene
当然,绝对没问题,我正在寻找的就是这个折中点。我不排除任何可能性,只是指出合理的碰撞避免并不一定意味着具有合理的均匀输出。我将不得不通过数学计算来确定lookup3是否除了良好的碰撞避免外还具有良好的均匀性。 - N. Virgo
我有另一个想法并加入了它。 - Gene

1
尝试使用指纹技术,例如Rabin指纹技术。
http://en.wikipedia.org/wiki/Fingerprint_(computing)。如果您选择了N位指纹,只需要将结果除以2^N即可。指纹是一种哈希函数,通常计算速度非常快(与MD5等加密哈希函数相比),但不适用于加密应用程序(通过其指纹可能会以某种方式恢复密钥值)。

谢谢 - 我之前没有听说过在这个上下文中使用“指纹”这个术语。 - N. Virgo

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