Коли хеш (n) == n у Python?


100

Я грав з хеш-функцією Python . Для малих цілих чисел воно з’являється hash(n) == nзавжди. Однак це не поширюється на велику кількість:

>>> hash(2**100) == 2**100
False

Я не здивований, я розумію, що хеш приймає обмежений діапазон значень. Що це за діапазон?

Я намагався використовувати двійковий пошук, щоб знайти найменше числоhash(n) != n

>>> import codejamhelpers # pip install codejamhelpers
>>> help(codejamhelpers.binary_search)
Help on function binary_search in module codejamhelpers.binary_search:

binary_search(f, t)
    Given an increasing function :math:`f`, find the greatest non-negative integer :math:`n` such that :math:`f(n) \le t`. If :math:`f(n) > t` for all :math:`n \ge 0`, return None.

>>> f = lambda n: int(hash(n) != n)
>>> n = codejamhelpers.binary_search(f, 0)
>>> hash(n)
2305843009213693950
>>> hash(n+1)
0

Що особливого у 2305843009213693951? Зауважу, це менше, ніжsys.maxsize == 9223372036854775807

Редагувати: я використовую Python 3. Я запустив той самий двійковий пошук на Python 2 і отримав інший результат 2147483648, який я зазначаю: sys.maxint+1

Я також грав з [hash(random.random()) for i in range(10**6)]оцінкою діапазону хеш-функції. Макс послідовно нижче n вище. Порівнюючи мінус, здається, хеш Python 3 завжди оцінюється позитивно, тоді як хеш Python 2 може приймати негативні значення.


9
Ви перевірили двійкове представлення номера?
— Джон Дворак

3
'0b111111111111111111111111111111111111111111111111111111111111111' цікаво! Отже n+1 == 2**61-1
— полковник Паніка

2
здається, залежить від системи. З моїм пітоном хеш призначений nдля всього діапазону 64-бітових int.
— Даніель

1
Зверніть увагу на заявлену мету хеш-значення: Вони використовуються для швидкого порівняння клавіш словника під час пошуку словника. Іншими словами, визначені реалізацією, і внаслідок того, що вони коротші, ніж багато значень, які можуть мати хеш-значення, цілком можуть мати зіткнення навіть у розумних місцях введення.
— CVn

2
Гм, не 2147483647дорівнює sys.maxint(не sys.maxint+1), і якщо 'n = 0b1111111111111111111111111111111111111111111111111111111111111', то це не ( n+1 == 2**61або n == 2**61-1ні n+1 == 2**61-1)?
— фог

Відповіді:


73

На основі документації python у pyhash.cфайлі:

Для числових типів хеш числа x заснований на зменшенні x модуля простим P = 2**_PyHASH_BITS - 1. Він розроблений так, що hash(x) == hash(y)коли x і y чисельно рівні, навіть якщо x і y мають різні типи.

Отже, для 64/32-бітної машини скорочення було б 2 _PyHASH_BITS - 1, але що таке _PyHASH_BITS?

Ви можете знайти його у pyhash.hзаголовковому файлі, який для 64-бітної машини був визначений як 61 (ви можете прочитати більше пояснень у pyconfig.hфайлі).

#if SIZEOF_VOID_P >= 8
#  define _PyHASH_BITS 61
#else
#  define _PyHASH_BITS 31
#endif

Тож спочатку все, що базується на вашій платформі, наприклад, на моїй 64-бітній платформі Linux зменшення становить 2 61 -1, а саме 2305843009213693951:

>>> 2**61 - 1
2305843009213693951

Також ви можете використовувати math.frexpдля того, щоб отримати мантісу та показник sys.maxintякої для 64-бітної машини показує, що max int становить 2 63 :

>>> import math
>>> math.frexp(sys.maxint)
(0.5, 64)

І ви можете побачити різницю за допомогою простого тесту:

>>> hash(2**62) == 2**62
True
>>> hash(2**63) == 2**63
False

Прочитайте повну документацію про алгоритм хешування пітонів https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

Як зазначено в коментарі, ви можете використовувати sys.hash_info(у python 3.X), що дасть вам структурну послідовність параметрів, що використовуються для обчислення хешей.

>>> sys.hash_info
sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003, algorithm='siphash24', hash_bits=64, seed_bits=128, cutoff=0)
>>> 

Поряд з модулем, який я описав у попередніх рядках, ви також можете отримати infзначення наступним чином:

>>> hash(float('inf'))
314159
>>> sys.hash_info.inf
314159

3
Було б непогано згадати sys.hash_info, для повноти.
— Марк Дікінсон

78

2305843009213693951є 2^61 - 1. Це найбільший прем'єр Mersenne, який входить у 64 біти.

Якщо вам доведеться зробити хеш, просто взявши значення mod деяке число, то великий прайм Mersenne - хороший вибір - його легко обчислити і забезпечити рівномірний розподіл можливостей. (Хоча я особисто ніколи б не робив хеш таким чином)

Особливо зручно обчислювати модуль для чисел з плаваючою точкою. Вони мають експоненціальну складову, яка множує ціле число на 2^x. Оскільки 2^61 = 1 mod 2^61-1вам потрібно лише врахувати (exponent) mod 61.

Дивіться: https://en.wikipedia.org/wiki/Mersenne_prime


8
Ви кажете, що ніколи так не зробили б хеш. Чи є у вас альтернативні пропозиції щодо того, як це можна зробити таким чином, щоб зробити обґрунтовано ефективним обчислення для ints, floats, Decimals, Fractions та гарантувати x == yгарантії для hash(x) == hash(y)різних типів? (Числа на кшталт Decimal('1e99999999')особливо проблемні, наприклад: вам не потрібно розгортати їх до відповідного цілого числа до хешування.)
— Марк Дікінсон

@MarkDickinson Я підозрюю, що він намагається розрізнити цей простий легкий швидкий хеш і криптографічні хеші, які також дбають про те, щоб результат виглядав випадковим.
— Майк Оунсворт

4
@MarkDickinson Модуль є гарним початком, але я б потім змішав його ще трохи, особливо змішуючи деякі високі біти в низькі. Не рідкість бачити послідовності цілих чисел, розділених на потужності 2. Не рідкість також бачити хеш-таблиці з ємністю, що мають потужність 2. На Java, наприклад, якщо у вас є послідовність цілих чисел, які діляться на 16, і ви використовуєте їх як ключі в HashMap, ви будете використовувати лише 1/16 відра (принаймні у версії джерела, на який я дивлюсь)! Я думаю, що хеші повинні бути хоч трохи випадковими, щоб уникнути цих проблем
— Метт Тіммерманс

Так, хеші стилів бітового змішування набагато перевершують математичні натхненні. Інструкції щодо змішування бітів настільки дешеві, що ви можете мати багато за ту ж ціну. Крім того, дані реального світу, схоже, не мають шаблонів, які не працюють добре при змішуванні бітів. Але є моделі, які жахливі для модуля.
— usr

9
@usr: Звичайно, але трохи змішування хеш нездійсненно тут: вимога про те , що хеш для роботи int, float, Decimalі Fractionоб'єкти , і що x == yмає на увазі , hash(x) == hash(y)навіть якщо xі yмають різні типи накладає досить жорсткі обмеження. Якби це лише питання написання хеш-функції для цілих чисел, не турбуючись про інші типи, це було б зовсім іншим питанням.
— Марк Дікінсон

9

Функція хеша повертає звичайний int, що означає, що повернене значення більше -sys.maxintі нижче sys.maxint, а значить, якщо ви перейдете sys.maxint + xдо нього, результатом буде -sys.maxint + (x - 2).

hash(sys.maxint + 1) == sys.maxint + 1 # False
hash(sys.maxint + 1) == - sys.maxint -1 # True
hash(sys.maxint + sys.maxint) == -sys.maxint + sys.maxint - 2 # True

Тим часом 2**200в nрази більше, sys.maxint- я гадаю, що хеш перейшов би діапазон -sys.maxint..+sys.maxintn разів, поки він не зупиниться на простому цілому числу в цьому діапазоні, як у фрагментах коду вище.

Тож загалом для будь-якого n <= sys.maxint :

hash(sys.maxint*n) == -sys.maxint*(n%2) +  2*(n%2)*sys.maxint - n/2 - (n + 1)%2 ## True

Примітка: це справедливо для python 2.


8
Це може бути справедливо для Python 2, але, безумовно, не для Python 3 (якого немає sys.maxint, і який використовує іншу хеш-функцію).
— interjay

0

Реалізацію для типу INT в CPython можна знайти тут.

Він просто повертає значення, крім -1, ніж воно повертає -2:

static long
int_hash(PyIntObject *v)
{
    /* XXX If this is changed, you also need to change the way
       Python's long, float and complex types are hashed. */
    long x = v -> ob_ival;
    if (x == -1)
        x = -2;
    return x;
}

6
Це не включає великі значення, які реалізуються, PyLongа не PyInt.
— interjay
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.