Khi nào thì hash (n) == n trong Python?


100

Tôi đã chơi với hàm băm của Python . Đối với các số nguyên nhỏ, nó hash(n) == nluôn xuất hiện . Tuy nhiên, điều này không mở rộng đến số lượng lớn:

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

Tôi không ngạc nhiên, tôi hiểu hàm băm có một phạm vi giá trị hữu hạn. Phạm vi đó là gì?

Tôi đã thử sử dụng tìm kiếm nhị phân để tìm số nhỏ nhấthash(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 có gì đặc biệt? Tôi lưu ý rằng nó ít hơnsys.maxsize == 9223372036854775807

Chỉnh sửa: Tôi đang sử dụng Python 3. Tôi đã chạy cùng một tìm kiếm nhị phân trên Python 2 và nhận được một kết quả khác 2147483648, tôi lưu ý là sys.maxint+1

Tôi cũng đã chơi với [hash(random.random()) for i in range(10**6)]để ước tính phạm vi của hàm băm. Giá trị lớn nhất luôn ở dưới n ở trên. So sánh tối thiểu, có vẻ như hàm băm của Python 3 luôn có giá trị dương, trong khi hàm băm của Python 2 có thể nhận các giá trị âm.


9
Bạn đã kiểm tra biểu diễn nhị phân của số chưa?
John Dvorak

3
'0b1111111111111111111111111111111111111111111111111111111111111' tò mò! Vì vậy n+1 == 2**61-1
Colonel Panic

2
dường như phụ thuộc vào hệ thống. Với python của tôi, hàm băm ndành cho toàn bộ dải int 64 bit.
Daniel

1
Lưu ý mục đích đã nêu của giá trị băm: Chúng được sử dụng để so sánh nhanh các khóa từ điển trong khi tra cứu từ điển. Nói cách khác, việc triển khai được xác định và do ngắn hơn nhiều giá trị có thể có giá trị băm, rất có thể có xung đột ngay cả trong không gian đầu vào hợp lý.
một CVn

2
Ừm, không 2147483647bằng sys.maxint(not sys.maxint+1) và nếu 'n = 0b111111111111111111111111111111111111111111111111111111111111111' thì không phải n+1 == 2**61hay n == 2**61-1(không n+1 == 2**61-1)?
phoog

Câu trả lời:


73

Dựa trên tài liệu python trong pyhash.ctệp:

Đối với kiểu số, hàm băm của một số x dựa trên việc rút gọn x modul thành số nguyên tố P = 2**_PyHASH_BITS - 1. Nó được thiết kế để hash(x) == hash(y)bất cứ khi nào x và y bằng nhau về số, ngay cả khi x và y có các kiểu khác nhau.

Vì vậy, đối với máy 64/32 bit, mức giảm sẽ là 2 _PyHASH_BITS - 1, nhưng là _PyHASH_BITSgì?

Bạn có thể tìm thấy nó trong pyhash.htệp tiêu đề dành cho máy 64 bit đã được định nghĩa là 61 (bạn có thể đọc thêm giải thích trong pyconfig.htệp).

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

Vì vậy, trước hết nó dựa trên nền tảng của bạn, ví dụ trong nền tảng Linux 64bit của tôi, mức giảm là 2 61 -1, đó là 2305843009213693951:

>>> 2**61 - 1
2305843009213693951

Ngoài ra, Bạn có thể sử dụng math.frexpđể lấy phần định trị và số mũ sys.maxintđối với máy 64 bit cho thấy rằng số nguyên tối đa là 2 63 :

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

Và bạn có thể thấy sự khác biệt bằng một bài kiểm tra đơn giản:

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

Đọc tài liệu đầy đủ về thuật toán băm python https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

Như đã đề cập trong nhận xét, bạn có thể sử dụng sys.hash_info(trong python 3.X), nó sẽ cung cấp cho bạn một chuỗi cấu trúc các tham số được sử dụng để tính toán hàm băm.

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

Cùng với mô-đun mà tôi đã mô tả trong các dòng trước, bạn cũng có thể nhận được infgiá trị như sau:

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

3
Sẽ rất tốt nếu được đề cập đến sys.hash_info, vì sự hoàn chỉnh.
Mark Dickinson

78

23058430092136939512^61 - 1. Đó là số nguyên tố Mersenne lớn nhất vừa với 64 bit.

Nếu bạn phải tạo một băm chỉ bằng cách lấy giá trị mod một số nào đó, thì một số nguyên tố Mersenne lớn là một lựa chọn tốt - nó dễ dàng tính toán và đảm bảo phân phối đều các khả năng. (Mặc dù cá nhân tôi sẽ không bao giờ thực hiện băm theo cách này)

Nó đặc biệt thuận tiện để tính toán mô-đun cho các số dấu phẩy động. Chúng có một thành phần cấp số nhân nhân số nguyên với 2^x. Vì 2^61 = 1 mod 2^61-1, bạn chỉ cần xem xét (exponent) mod 61.

Xem: https://en.wikipedia.org/wiki/Mersenne_prime


8
Bạn nói rằng bạn sẽ không bao giờ thực hiện băm theo cách này. Bạn có đề xuất thay thế về cách nó có thể được thực hiện theo cách làm cho nó hiệu quả hợp lý để tính số nguyên, số thực, số thập phân, phân số đảm bảo rằng x == yđảm bảo hash(x) == hash(y)giữa các loại không? (Các số như thế Decimal('1e99999999')đặc biệt có vấn đề, ví dụ: bạn không muốn phải mở rộng chúng thành số nguyên tương ứng trước khi băm.)
Mark Dickinson

@MarkDickinson Tôi nghi ngờ anh ấy đang cố gắng phân biệt giữa hàm băm nhanh làm sáng đơn giản này và hàm băm mật mã cũng quan tâm đến việc làm cho đầu ra trông ngẫu nhiên.
Mike Ounsworth

4
@MarkDickinson Mô-đun là một khởi đầu tốt, nhưng sau đó tôi sẽ trộn thêm một số nữa, đặc biệt là trộn một số bit cao vào mức thấp. Không có gì lạ khi thấy chuỗi các số nguyên chia hết cho các lũy thừa của 2. Cũng không có gì lạ khi bạn thấy các bảng băm có dung lượng là các lũy thừa của 2. Ví dụ: Trong Java, nếu bạn có một chuỗi các số nguyên chia hết cho 16, và bạn sử dụng chúng làm khóa trong HashMap, bạn sẽ chỉ sử dụng 1/16 nhóm (ít nhất là trong phiên bản nguồn mà tôi đang xem)! Tôi nghĩ băm phải có ít nhất một chút cắn ngẫu nhiên tìm cách để tránh những problerms
Matt Timmermans

Đúng vậy, các hàm băm kiểu trộn bit vượt trội hơn nhiều so với các hàm lấy cảm hứng từ toán học. Hướng dẫn trộn bit rất rẻ nên bạn có thể có nhiều hướng dẫn với cùng chi phí. Ngoài ra, dữ liệu thế giới thực dường như không có các mẫu không hoạt động tốt với việc trộn bit. Nhưng có những mẫu rất kinh khủng đối với mô đun.
usr

9
@usr: Chắc chắn, nhưng một hash-bit trộn là không khả thi ở đây: yêu cầu rằng công việc băm cho int, float, DecimalFractioncác đối tượng và điều đó x == ycó nghĩa hash(x) == hash(y)ngay cả khi xycó các loại khác nhau áp đặt một số hạn chế khá là nghiêm trọng. Nếu chỉ là vấn đề viết một hàm băm cho các số nguyên mà không cần lo lắng về các kiểu khác, thì đó sẽ là một vấn đề hoàn toàn khác.
Mark Dickinson

9

Hàm băm trả về giá trị int đơn giản có nghĩa là giá trị trả về lớn hơn -sys.maxintvà thấp hơn sys.maxint, có nghĩa là nếu bạn chuyển sys.maxint + xđến nó thì kết quả sẽ là -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

Trong khi đó lớn hơn 2**200một nlần sys.maxint- tôi đoán là băm sẽ vượt quá phạm vi -sys.maxint..+sys.maxintn lần cho đến khi nó dừng lại trên số nguyên thuần túy trong phạm vi đó, như trong các đoạn mã ở trên ..

Vì vậy, nói chung, với bất kỳ n <= sys.maxint :

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

Lưu ý: điều này đúng với python 2.


8
Điều này có thể đúng với Python 2, nhưng chắc chắn không đúng với Python 3 (không có sys.maxintvà sử dụng một hàm băm khác).
giữa

0

Việc triển khai kiểu int trong cpython có thể được tìm thấy tại đây.

Nó chỉ trả về giá trị, ngoại trừ -1, hơn nó trả về -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
Điều này không bao gồm các giá trị lớn, được triển khai bởi PyLongthay vì PyInt.
giữa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.