Python - 获取随机颜色,尽快给出种子编号



我需要找到一种随机颜色,给定一个特定的种子编号 - 快速。 给定相同的 ID 两次,应返回相同的颜色。

我这样做了:

def id_to_random_color(number):
random_bytes = hashlib.sha1(bytes(number)).digest()
return [int(random_bytes[-1]) / 255, int(random_bytes[-2]) / 255, int(random_bytes[-3]) / 255, 1.0]

问题是多次计算数字的 sha1 总体上非常慢。(我使用这个函数大约 100k 次(

编辑:我使用哈希函数的原因是,我希望对于接近的数字有不同的颜色

例如,id_to_random_color(7)应该与id_to_random_color(9)非常不同

使用带有静态变量的简单随机数生成器可以提高性能:

import random
prev, r, g, b = None, 0, 0, 0
def id_to_random_color(number):
global prev, r, g, b
if number != prev:
r = random.random()
g = random.random()
b = random.random()
prev = number
return r, g, b, 1.0

更新:
正如AndrewMcDowell在他的评论中所说,如果输入在非顺序场合重复,该函数可能会返回不同的值。
下面是一个可能的解决方法:

import random
memory = {}
def id_to_random_color(number, memory):
if not number in memory:
r = random.random()
g = random.random()
b = random.random()
memory[number] = (r, g, b, 1.0)
return memory[number]

进一步更新:相同的函数骨架甚至可以用于计算哈希:

memory = {}
def id_to_random_color(number):
if not number in memory:
numByte = str.encode(number)
hashObj = hashlib.sha1(numByte).digest()
r, g, b = hashObj[-1] / 255.0, hashObj[-2] / 255.0, hashObj[-3] / 255.0
memory[number]= (r, g, b, 1.0)
return r, g, b, 1.0
else:
return memory[number]

尽管语法更冗长一些,但else语句提高了一点性能,避免了随后的内存写入和读取(正如 Jake 在他的回答中所说(。

你没有提到number的范围。它必须是非负整数,否则bytes(number)将失败。(顺便说一句,该函数返回一个由number零字节组成的bytes字符串,如果number很大,这将占用大量 RAM(。我认为number至少是 24 位来覆盖 24 位 RGB 色彩空间。

为此目的使用加密哈希函数是矫枉过正的。OTOH,hashlib函数非常快,因为它们是用 C 编码的。我们可以利用built_inhash函数,但是hash(n)只是为机器大小的整数返回n,因此我们需要执行类似hash((n, n))的操作才能获得随机外观的输出。然而,做这种事情的结果并不是特别随机的:hash是为哈希表工作而设计的,而不是为我们在这里想要的那种加扰而设计的。

为了生成随机RGB值,我改编了Yann Collet的xxHash混合算法。您可以在 xxhash.c 源代码中查看该算法的 C 源代码。该算法相当快,并且具有良好的雪崩。Bret Mulvey写了一篇关于哈希混合函数和雪崩效应的很好的介绍性文章。

def id_to_random_color(n):
n = ((n ^ n >> 15) * 2246822519) & 0xffffffff
n = ((n ^ n >> 13) * 3266489917) & 0xffffffff
n = (n ^ n >> 16) >> 8
return [u / 255. for u in n.to_bytes(3, 'big')] + [1.0]

这个函数在range(2**24)n工作得很好,事实上它的结果在整个range(2**32)中都相当不错;它仍然会给出超出该范围的结果。为了在这里进行测试,我将使用一个简化版本,它将 RGB 值作为整数返回。第一个测试仅显示range(20)n的 RGB 值。第二个测试生成 25600 个随机数并找到相应的 RGB 值。我们应该为每个 R、G 和 B 值获得大约 100 次点击。

from collections import Counter
from random import seed, randrange
seed(42)
def id_to_RGB(n):
n = ((n ^ n >> 15) * 2246822519) & 0xffffffff
n = ((n ^ n >> 13) * 3266489917) & 0xffffffff
n = (n ^ n >> 16) >> 8
return tuple(n.to_bytes(3, 'big'))
# Tests
# Show some colors
for i in range(20):
rgb = id_to_RGB(i)
print('{:2d}: {:02x} {:02x} {:02x}'.format(i, *rgb))
print()
# Count the frequency of each color for random `n`
counts = {k: Counter() for k in 'rgb'}
for i in range(25600):
n = randrange(2 ** 32)
for k, v in zip('rgb', id_to_RGB(n)):
counts[k][v] += 1
for k in 'rgb':
print(k, sorted(counts[k].values()))

输出

0: 00 00 00
1: 60 6d 18
2: 4e f2 bf
3: 75 4f 48
4: 60 98 f1
5: 17 1d 98
6: 3b 69 13
7: aa 10 98
8: c1 31 e3
9: 1e fa 4a
10: 7f 05 b2
11: 86 0e b3
12: 39 84 c6
13: c1 75 4f
14: e2 38 87
15: db 54 79
16: 45 14 b6
17: cb 56 68
18: 8e bf d8
19: cd 50 3f

计数器输出

r [74, 75, 75, 77, 78, 80, 80, 80, 80, 81, 82, 83, 84, 85, 85, 85, 86, 86, 86, 88, 88, 88, 88, 89, 89, 89, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 104, 104, 104, 104, 104, 104, 104, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 106, 106, 106, 106, 106, 106, 106, 106, 106, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 108, 108, 108, 108, 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 110, 110, 110, 110, 110, 110, 110, 110, 111, 112, 112, 112, 112, 112, 113, 113, 113, 114, 114, 115, 115, 115, 115, 116, 116, 116, 116, 118, 119, 120, 123, 124, 126, 128, 138]
g [73, 74, 74, 77, 78, 79, 79, 81, 81, 82, 82, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 86, 87, 87, 87, 87, 87, 87, 87, 88, 88, 88, 88, 88, 89, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 101, 101, 101, 101, 101, 101, 102, 102, 102, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103, 103, 103, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 106, 106, 106, 106, 106, 106, 106, 107, 107, 107, 107, 107, 107, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 110, 110, 111, 111, 111, 111, 111, 111, 112, 112, 112, 112, 112, 112, 113, 113, 113, 113, 113, 113, 113, 113, 114, 114, 114, 114, 115, 115, 116, 117, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 121, 121, 121, 123, 125, 126, 128]
b [73, 74, 77, 78, 78, 79, 80, 80, 80, 81, 82, 84, 84, 84, 84, 84, 84, 84, 84, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 89, 89, 89, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 92, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 101, 101, 101, 101, 101, 101, 101, 101, 101, 102, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103, 103, 103, 103, 103, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 105, 105, 105, 105, 105, 105, 105, 106, 106, 106, 106, 106, 106, 106, 107, 107, 107, 107, 107, 107, 107, 107, 107, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, 109, 110, 110, 110, 111, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 114, 114, 115, 115, 115, 115, 115, 115, 115, 115, 115, 116, 116, 116, 117, 118, 119, 120, 120, 122, 124, 126, 127, 128, 131]

您可能会注意到,对于零输入,id_to_RGB返回所有零。如果这不可取,您可以在开始时添加一个额外的混合步骤(也借用自 xxHash(。

def id_to_RGB(n):
n = (374761397 + n * 3266489917) & 0xffffffff    
n = ((n ^ n >> 15) * 2246822519) & 0xffffffff
n = ((n ^ n >> 13) * 3266489917) & 0xffffffff
n = (n ^ n >> 16) >> 8
return tuple(n.to_bytes(3, 'big'))

我会使用字典来快速索引已经生成的种子。

import random
random_seeds = {}
def id_to_random_color(number):
if number in random_seeds.keys():
return random_seeds[number]
else:
color = [random.random(), random.random(), random.random(), 1.0]
random_seeds[number] = color
return color

最新更新