[英]Python - Get random color, given a seed number as fast as possible
我需要找到一个随机颜色,给定一个特定的种子数 - 快。 给两次相同的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在他的回答中所述)。
我会使用字典快速索引已经生成的种子。
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
你没有提到的范围number
。 它必须是非负整数,否则bytes(number)
将失败。 (顺便说一句,该函数返回一个由number
零字节组成的bytes
字符串,如果number
很大,将会占用大量的RAM)。 我认为number
至少是24位,以覆盖24位RGB颜色空间。
为此目的使用加密哈希函数是过度的。 OTOH, hashlib
函数非常快,因为它们是用C编码的。我们可以使用built_in hash
函数,但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**24)
,实际上它的结果在整个range(2**32)
非常好range(2**32)
; 它仍会在该范围之外提供有用的结果。 要在此测试,我将使用一个简化版本,将RGB值作为整数返回。 第一个测试只显示range(20)
n
的RGB值range(20)
。 第二个测试生成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'))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.