【问题标题】:Python - Get random color, given a seed number as fast as possiblePython - 尽可能快地获取随机颜色,给定种子数
【发布时间】:2019-03-15 12:42:18
【问题描述】:

我需要找到一个随机的颜色,给定一个特定的种子数 - 快。 给定相同的 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 总起来很慢。 (我正在使用这个功能大约 10 万次)

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

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

【问题讨论】:

  • 忘记散列函数,直接使用字节 - 如果所有位都已经是随机的,散列函数有什么好处?
  • 不是很有帮助,但供其他人参考,我将其与固定种子值后仅生成 3 个随机整数进行比较,这样做比上面的哈希方法慢约 30%。
  • 您可以创建一个将整数映射到颜色的np.array:每个颜色组件(例如RGB)都可以使用np.random.randint(256, size=large_number) 创建并通过索引number 轻松访问
  • 仅使用 random.random() 为三个索引中的每一个获取一个介于 0 和 1 之间的值,我确实得到了轻微的改进。
  • number的范围是多少?顺便说一句,您可能不想要bytes(number),因为这会返回一个bytes 长度为零的字符串number。

标签: python python-3.x random hash


【解决方案1】:

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

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 在他的回答中所说)。

【讨论】:

  • 我做了一些计时,这在我的机器上通常比原始帖子快 3 倍,但如果您连续两次执行相同的值,这只会给出相同的答案。如果你有 number = 10,然后是 12,然后是 10,那么 number = 10 的两个调用的返回值将是不同的。
  • @AndrewMcDowell,是的,这个简单的代码块只能记住一个先前的输入。可以使用数据结构(例如字典)来改善内存,因此您提到的问题可以部分解决;但是如果 OP 正在寻找输入和输出值之间的严格关系,那么谈论 随机颜色 是不正确的。
【解决方案2】:

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

为此目的使用加密哈希函数是多余的。 OTOH,hashlib 函数非常快,因为它们是用 C 编码的。我们可以利用内置的 hash 函数,但是 hash(n) 只返回 n 用于机器大小的整数,所以我们需要做类似hash((n, n)) 的事情来获得看起来随机的输出。然而,做这类事情的结果并不是特别随机:hash 是为哈希表工作而设计的,而不是我们想要的那种加扰。

为了生成随机 RGB 值,我采用了 Yann Collet 的 xxHash 中的混合算法。您可以在xxhash.c source code 中查看该算法的 C 源代码。这个算法相当快,它有很好的avalanching。 Bret Mulvey 写了一篇关于hash mixing functions and the avalanche effect 的很好的介绍性文章。

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'))

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 2023-03-31
      • 2012-02-23
      • 2012-02-13
      • 2018-10-22
      • 1970-01-01
      • 2015-05-25
      • 2011-12-29
      • 2014-06-12
      • 2016-01-11
      相关资源
      最近更新 更多