Python - Получить случайный цвет, учитывая число семян как можно быстрее - PullRequest
0 голосов
/ 10 октября 2018

Мне нужно найти случайный цвет, учитывая конкретное число семян - быстро.учитывая один и тот же идентификатор дважды, должен возвращаться один и тот же цвет.

Я сделал это:

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 чисел много раз очень медленное в целом.(Я использую эту функцию около 100 тыс. Раз)

Редактировать: причина, по которой я использую хэш-функцию, заключается в том, что я хочу, чтобы цвета были разными для близких чисел

например id_to_random_color(7) должно сильно отличаться от id_to_random_color(9)

Ответы [ 3 ]

0 голосов
/ 10 октября 2018

Я бы использовал словарь для быстрой индексации уже сгенерированных семян.

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
0 голосов
/ 10 октября 2018

Вы не упомянули диапазон number.Это должно быть неотрицательное целое число, иначе bytes(number) потерпит неудачу.(Кстати, эта функция возвращает строку bytes, состоящую из number нулевых байтов, которая потратит много оперативной памяти, если number велико).Я предполагаю, что number составляет не менее 24 бит, чтобы покрыть 24-битное цветовое пространство RGB.

Использование криптографической хеш-функции для этой цели является излишним.OTOH, функции hashlib довольно быстрые, так как они кодируются на C. Мы могли бы использовать функцию built_in hash, однако hash(n) просто возвращает n для целых чисел машинного размера,поэтому нам нужно сделать что-то вроде hash((n, n)), чтобы получить случайный результат.Однако результаты такого рода действий не являются случайными: hash предназначен для работы с хеш-таблицами, а не для скремблирования, которое мы хотим здесь.

Для генерации случайных значений RGB я адаптировал алгоритм микширования из xxHash от Yann Collet.Вы можете просмотреть исходный код C этого алгоритма в исходном коде xxhash.c .Этот алгоритм достаточно быстрый и имеет хорошее лавинное .Брет Малви написал хорошую вводную статью о функциях смешивания хэшей и лавинном эффекте .

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]

Эта функция хорошо работает для n в range(2**24), и на самом деле ее результатыдовольно хороши в целом range(2**32);это все еще даст полезные результаты за пределами этого диапазона.Чтобы проверить это здесь, я буду использовать упрощенную версию, которая возвращает значения RGB в виде целых чисел.Первый тест просто показывает значения RGB для n в range(20).Второй тест генерирует 25600 случайных чисел и находит соответствующие значения RGB.Мы должны получить примерно 100 совпадений для каждого значения R, G и B.

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'))
0 голосов
/ 10 октября 2018

Использование простых генераторов случайных чисел с видом статических переменных может повысить производительность:

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]

Дальнейшее обновление:
Один и тот же скелет функции можно использовать даже для вычисленияhash:

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 улучшает производительность битов, избегая последующей записи и чтения в память (как сказал Джейк в своем ответе).

...