繁体   English   中英

Python - 获取随机颜色,尽可能快地给出种子数

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM