Коли хеш (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.