Преобразование строки в случайную, но детерминированно воспроизводимую единую вероятность

Как преобразовать строку, например. идентификатор пользователя плюс соль к случайной, но на самом деле детерминистически повторяемой равномерной вероятности в полуоткрытом диапазоне [0,0, 1,0)? Это означает, что результат равен ≥ 0,0 и ‹ 1,0. Выходное распределение должно быть равномерным независимо от входного распределения. Например, если входная строка — «a3b2Foobar», выходная вероятность может быть равна 0,40341504.

Желательна межъязыковая и кроссплатформенная алгоритмическая воспроизводимость. Я склонен использовать хеш-функцию, если нет лучшего способа. Вот что у меня есть:

>>> in_str = 'a3b2Foobar'
>>> (int(hashlib.sha256(in_str.encode()).hexdigest(), 16) % 1e8) / 1e8
0.40341504

Я использую последнюю стабильную версию Python 3. Обратите внимание, что этот вопрос похож, но не совсем идентичен связанному вопросу с преобразовать целое число в случайный, но детерминистически повторяемый выбор.


person Acumenus    schedule 14.06.2017    source источник
comment
На самом деле ваш вопрос не отличается от преобразования целого числа в случайный, но детерминированно повторяемый выбор - после того, как вы преобразовали свою строку в целочисленный эквивалент с помощью хеширования, это точно такая же проблема.   -  person Chris Johnson    schedule 21.06.2017
comment
@ChrisJohnson Хорошо, но ответ существенно отличается. Один использует модуль, другой нет. Сказав это, я думаю, что в другом ответе можно полностью избежать использования модуля - вместо этого линейно масштабируя хэш-значение до количества доступных вариантов.   -  person Acumenus    schedule 23.06.2017


Ответы (1)


Использование хэша

Криптографический хэш предположительно представляет собой равномерно распределенное целое число в диапазоне [0, MAX_HASH]. Соответственно, его можно масштабировать до числа с плавающей запятой в диапазоне [0, 1), разделив его на MAX_HASH + 1.

import hashlib

Hash = hashlib.sha512
MAX_HASH_PLUS_ONE = 2**(Hash().digest_size * 8)

def str_to_probability(in_str):
    """Return a reproducible uniformly random float in the interval [0, 1) for the given string."""
    seed = in_str.encode()
    hash_digest = Hash(seed).digest()
    hash_int = int.from_bytes(hash_digest, 'big')  # Uses explicit byteorder for system-agnostic reproducibility
    return hash_int / MAX_HASH_PLUS_ONE  # Float division

>>> str_to_probability('a3b2Foobar')
0.3659629991207491

Вот реальный мир пример использования< /а>.

Заметки:

  • Встроенный метод hash нельзя использовать, так как он может сохранить распределение ввода, например. с hash(123). В качестве альтернативы он может возвращать значения, которые отличаются при перезапуске Python, например. с hash('123').
  • Использование по модулю не обязательно, так как деления с плавающей запятой достаточно.

Использование случайного

Модуль random можно использовать с in_str в качестве начального значения, решая проблемы, связанные с обоими < href="https://stackoverflow.com/questions/10021882/how-do-i-make-randint-threadsafe-in-python">безопасность потоков и непрерывность.

При таком подходе проблемой может стать не только воспроизводимость на разных языках, но и воспроизводимость в нескольких будущих версиях Python. Поэтому не рекомендуется.

import random

def str_to_probability(in_str):
    """Return a reproducible uniformly random float in the interval [0, 1) for the given seed."""
    return random.Random(in_str).random()

>>> str_to_probability('a3b2Foobar')
0.4662507245848473
person Acumenus    schedule 14.06.2017
comment
Я согласен с решением hashlib. Тем более, что SHA512 будет реализован на нескольких платформах. SHA используется для шифрования, и поэтому он будет наиболее близок к случайному, но повторяемому, который вы собираетесь получить. Хотя вы можете рассмотреть и другие схемы шифрования, и самое главное, вы никогда не должны внедрять свои собственные. - person VoNWooDSoN; 20.06.2017
comment
Этот ответ предполагает Python 3. В python 2 вам нужно преобразовать один из входных данных в число с плавающей запятой, чтобы получить деление с плавающей запятой. - person SpliFF; 23.06.2017