ฟังก์ชันแฮชจำนวนเต็มอะไรดีที่รับคีย์แฮชจำนวนเต็ม
ฟังก์ชันแฮชจำนวนเต็มอะไรดีที่รับคีย์แฮชจำนวนเต็ม
คำตอบ:
วิธีการคูณของ Knuth:
hash(i)=i*2654435761 mod 2^32
โดยทั่วไปคุณควรเลือกตัวคูณที่อยู่ในลำดับขนาดแฮชของคุณ (2^32
ในตัวอย่าง) และไม่มีปัจจัยร่วมด้วย ด้วยวิธีนี้ฟังก์ชันแฮชจะครอบคลุมพื้นที่แฮชทั้งหมดของคุณอย่างสม่ำเสมอ
แก้ไข: ข้อเสียที่ใหญ่ที่สุดของฟังก์ชันแฮชนี้คือการรักษาความสามารถในการหารดังนั้นหากจำนวนเต็มของคุณหารด้วย 2 หรือ 4 ได้ทั้งหมด (ซึ่งไม่ใช่เรื่องแปลก) แฮชของพวกเขาก็จะมากเกินไป นี่เป็นปัญหาในตารางแฮช - คุณสามารถใช้เพียง 1/2 หรือ 1/4 ของที่เก็บข้อมูลเท่านั้น
ฉันพบว่าอัลกอริทึมต่อไปนี้มีการแจกแจงทางสถิติที่ดีมาก บิตอินพุตแต่ละบิตมีผลต่อบิตเอาต์พุตแต่ละบิตโดยมีความน่าจะเป็นประมาณ 50% ไม่มีการชนกัน (แต่ละอินพุตส่งผลให้เอาต์พุตต่างกัน) อัลกอริทึมทำงานได้อย่างรวดเร็วยกเว้นว่า CPU ไม่มีหน่วยการคูณจำนวนเต็มในตัว รหัส C สมมติว่าint
เป็น 32 บิต (สำหรับ Java แทนที่>>
ด้วย>>>
และลบunsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
ตัวเลขมหัศจรรย์คำนวณโดยใช้โปรแกรมทดสอบแบบมัลติเธรดพิเศษที่ทำงานเป็นเวลาหลายชั่วโมงซึ่งคำนวณเอฟเฟกต์หิมะถล่ม (จำนวนบิตเอาต์พุตที่เปลี่ยนแปลงหากบิตอินพุตเดียวมีการเปลี่ยนแปลงควรเฉลี่ยเกือบ 16) ความเป็นอิสระของ การเปลี่ยนแปลงบิตเอาต์พุต (บิตเอาต์พุตไม่ควรขึ้นอยู่กับกันและกัน) และความน่าจะเป็นของการเปลี่ยนแปลงในแต่ละบิตเอาต์พุตหากบิตอินพุตมีการเปลี่ยนแปลง ค่าที่คำนวณได้ดีกว่า finalizer 32 บิตใช้โดยMurmurHashและเกือบเป็นดี (ไม่มาก) เช่นเมื่อใช้AES ข้อดีเล็กน้อยคือใช้ค่าคงที่เท่ากันสองครั้ง (ทำให้เร็วขึ้นเล็กน้อยในครั้งสุดท้ายที่ฉันทดสอบไม่แน่ใจว่ายังเป็นเช่นนั้นอยู่หรือไม่)
คุณสามารถย้อนกลับกระบวนการ (รับค่าอินพุตจากแฮช) หากคุณแทนที่0x45d9f3b
ด้วย0x119de1f3
( ผกผันการคูณ ):
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
สำหรับหมายเลข 64 บิตฉันขอแนะนำให้ใช้สิ่งต่อไปนี้แม้ว่าจะคิดว่าอาจไม่เร็วที่สุด อันนี้อ้างอิงจากSplitmix64ซึ่งดูเหมือนจะมาจากบทความบล็อกBetter Bit Mixing (ผสม 13)
uint64_t hash(uint64_t x) {
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
x = x ^ (x >> 31);
return x;
}
สำหรับ Java, การใช้งานlong
เพิ่มL
เพื่อคงแทนที่>>
ด้วยและลบ>>>
unsigned
ในกรณีนี้การย้อนกลับมีความซับซ้อนมากขึ้น:
uint64_t unhash(uint64_t x) {
x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
x = x ^ (x >> 30) ^ (x >> 60);
return x;
}
อัปเดต: คุณอาจต้องการดูโครงการHash Function Prospectorซึ่งมีการระบุค่าคงที่อื่น ๆ (อาจดีกว่า)
x = ((x >> 32) ^ x)
แล้วใช้การคูณ 32 บิตด้านบน ฉันไม่แน่ใจว่าอะไรดีขึ้น คุณอาจต้องการดูFinalizer 64 บิตสำหรับ Murmur3
ขึ้นอยู่กับวิธีการกระจายข้อมูลของคุณ สำหรับเคาน์เตอร์ธรรมดาฟังก์ชันที่ง่ายที่สุด
f(i) = i
จะดี (ฉันสงสัยว่าดีที่สุด แต่ฉันไม่สามารถพิสูจน์ได้)
.hashCode()
ดูที่นี่
ฟังก์ชันแฮชที่รวดเร็วและดีสามารถประกอบด้วยการเรียงสับเปลี่ยนอย่างรวดเร็วที่มีคุณสมบัติน้อยกว่าเช่น
เพื่อให้ได้ฟังก์ชันการแฮชที่มีคุณสมบัติที่เหนือกว่าเช่นที่แสดงด้วยPCGสำหรับการสร้างตัวเลขแบบสุ่ม
อันที่จริงแล้วนี่คือสูตร rrxmrrxmsx_0 และแฮชบ่นกำลังใช้โดยไม่รู้ตัวหรือไม่รู้
ฉันเองพบ
uint64_t xorshift(const uint64_t& n,int i){
return n^(n>>i);
}
uint64_t hash(const uint64_t& n){
uint64_t p = 0x5555555555555555ull; // pattern of alternating 0 and 1
uint64_t c = 17316035218449499591ull;// random uneven integer constant;
return c*xorshift(p*xorshift(n,32),32);
}
จะดีพอ
ฟังก์ชันแฮชที่ดีควร
ก่อนอื่นเรามาดูฟังก์ชันเอกลักษณ์ เป็นไปตาม 1. แต่ไม่ใช่ 2. :
บิตอินพุต n กำหนดบิตเอาต์พุต n โดยมีความสัมพันธ์ 100% (สีแดง) และไม่มีอื่น ๆ ดังนั้นจึงเป็นสีน้ำเงินทำให้มีเส้นสีแดงที่สมบูรณ์แบบ
xorshift (n, 32) ไม่ดีกว่ามากโดยให้ผลครึ่งบรรทัด ยังคงเป็นที่น่าพอใจ 1. เนื่องจากสามารถกลับด้านได้ด้วยแอปพลิเคชันที่สอง
การคูณด้วยจำนวนเต็มที่ไม่ได้ลงนามจะดีกว่ามากเรียงซ้อนกันแรงกว่าและพลิกบิตเอาต์พุตมากขึ้นโดยมีความน่าจะเป็น 0.5 ซึ่งเป็นสิ่งที่คุณต้องการเป็นสีเขียว เป็นไปตาม 1. สำหรับจำนวนเต็มที่ไม่เท่ากันแต่ละตัวจะมีผกผันการคูณ
การรวมทั้งสองอย่างให้ผลลัพธ์ต่อไปนี้ยังคงเป็นที่น่าพอใจ 1. เนื่องจากองค์ประกอบของฟังก์ชัน bijective สองฟังก์ชันจะให้ฟังก์ชัน bijective อื่น
การประยุกต์ใช้การคูณและ xorshift ครั้งที่สองจะให้ผลดังนี้:
หรือคุณสามารถใช้การคูณฟิลด์ Galois เช่นGHashมันกลายเป็นซีพียูที่รวดเร็วพอสมควรและมีคุณสมบัติที่เหนือกว่าในขั้นตอนเดียว
uint64_t const inline gfmul(const uint64_t& i,const uint64_t& j){
__m128i I{};I[0]^=i;
__m128i J{};J[0]^=j;
__m128i M{};M[0]^=0xb000000000000000ull;
__m128i X = _mm_clmulepi64_si128(I,J,0);
__m128i A = _mm_clmulepi64_si128(X,M,0);
__m128i B = _mm_clmulepi64_si128(A,M,0);
return A[0]^A[1]^B[1]^X[0]^X[1];
}
__m128i I = i; //set the lower 64 bits
ดังนั้นจึงไม่ไม่ถูกบุกรุก เกี่ยวกับ initialisation กับคอมไพเลอร์ของฉันไม่เคยบ่นก็ไม่อาจจะเป็นทางออกที่ดีที่สุด แต่สิ่งที่ฉันต้องการมีที่อยู่ Initialise ทั้งหมดของมันให้เป็น 0 ดังนั้นฉันสามารถทำได้หรือ ฉันคิดว่าฉันใช้รหัสนั้นในบล็อกโพสต์นี้ซึ่งให้การผกผันที่มีประโยชน์มาก: D^=
0^1 = 1
{}
^=
|=
วิธีการคูณ 32 บิต (เร็วมาก) ดูที่ @rafal
#define hash32(x) ((x)*2654435761)
#define H_BITS 24 // Hashtable size
#define H_SHIFT (32-H_BITS)
unsigned hashtab[1<<H_BITS]
....
unsigned slot = hash32(x) >> H_SHIFT
32 บิตและ 64 บิต (การกระจายที่ดี) ที่: MurmurHash
หน้านี้แสดงฟังก์ชันแฮชแบบง่าย ๆ ซึ่งมีแนวโน้มที่จะเหมาะสมโดยทั่วไป แต่แฮชธรรมดา ๆ จะมีกรณีทางพยาธิวิทยาที่ไม่ได้ผลดี
มีภาพรวมที่ดีกว่าขั้นตอนวิธีกัญชาบางEternally Confuzzled ฉันขอแนะนำแฮชแบบครั้งละครั้งของ Bob Jenkins ซึ่งสามารถเข้าถึงได้อย่างรวดเร็วดังนั้นจึงสามารถใช้สำหรับการค้นหาตารางแฮชที่มีประสิทธิภาพ
คำตอบขึ้นอยู่กับหลายสิ่งเช่น:
ฉันขอแนะนำให้คุณดูฟังก์ชันแฮชตระกูลMerkle-Damgardเช่น SHA-1 เป็นต้น
ฉันไม่คิดว่าเราจะสามารถพูดได้ว่าฟังก์ชันแฮชนั้น "ดี" โดยไม่รู้ข้อมูลของคุณล่วงหน้า! และไม่รู้ว่าคุณกำลังจะทำอะไรกับมัน
มีโครงสร้างข้อมูลที่ดีกว่าตารางแฮชสำหรับขนาดข้อมูลที่ไม่รู้จัก (ฉันสมมติว่าคุณกำลังทำแฮชสำหรับตารางแฮชที่นี่) ฉันจะใช้ตารางแฮชเป็นการส่วนตัวเมื่อฉันรู้ว่าฉันมีองค์ประกอบ "จำกัด " จำนวนหนึ่งที่จำเป็นต้องจัดเก็บไว้ในหน่วยความจำจำนวน จำกัด ฉันจะลองทำการวิเคราะห์ทางสถิติอย่างรวดเร็วเกี่ยวกับข้อมูลของฉันดูว่ามีการกระจายอย่างไร ฯลฯ ก่อนที่ฉันจะเริ่มคิดถึงฟังก์ชันแฮชของฉัน
สำหรับค่าแฮชแบบสุ่มวิศวกรบางคนกล่าวว่าจำนวนเฉพาะของอัตราส่วนทองคำ (2654435761) เป็นตัวเลือกที่ไม่ดีด้วยผลการทดสอบของฉันฉันพบว่ามันไม่เป็นความจริง แทน 2654435761 กระจายค่าแฮชค่อนข้างดี
#define MCR_HashTableSize 2^10
unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
key = key*2654435761 & (MCR_HashTableSize - 1)
return key;
}
ขนาดตารางแฮชต้องมีกำลังสอง
ฉันได้เขียนโปรแกรมทดสอบเพื่อประเมินฟังก์ชันแฮชสำหรับจำนวนเต็มผลปรากฏว่า GRPrimeNumber เป็นตัวเลือกที่ดีทีเดียว
ฉันเหนื่อย:
จากผลการทดสอบของฉันฉันพบว่า Golden Ratio Prime Number มีที่เก็บข้อมูลที่ว่างน้อยกว่าหรือที่เก็บข้อมูลว่างเป็นศูนย์เสมอ
ฟังก์ชันแฮชบางฟังก์ชันสำหรับจำนวนเต็มถูกอ้างว่าดี แต่ผลการทดสอบแสดงให้เห็นว่าเมื่อ total_data_entry / total_bucket_number = 3 ความยาวโซ่ที่ยาวที่สุดจะมากกว่า 10 (จำนวนการชนสูงสุด> 10) และที่เก็บข้อมูลจำนวนมากจะไม่ถูกแมป (ที่เก็บข้อมูลว่าง ) ซึ่งแย่มากเมื่อเทียบกับผลของถังเปล่าเป็นศูนย์และความยาวโซ่ที่ยาวที่สุด 3 โดย Golden Ratio Prime Number Hashing
BTW จากผลการทดสอบของฉันฉันพบว่าฟังก์ชันแฮช shifting-xor เวอร์ชันหนึ่งค่อนข้างดี (แชร์โดย mikera)
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
ฉันใช้splitmix64
(ชี้ในคำตอบของ Thomas Mueller ) ตั้งแต่ฉันพบหัวข้อนี้ อย่างไรก็ตามเมื่อเร็ว ๆ นี้ฉันได้สะดุดกับrrxmrrxmsx_0ของ Pelle Evensen ซึ่งให้การกระจายทางสถิติที่ดีขึ้นอย่างมากเมื่อเทียบกับโปรแกรมสรุปขั้นสุดท้าย MurmurHash3 และตัวต่อ ( splitmix64
และส่วนผสมอื่น ๆ ) นี่คือข้อมูลโค้ดใน C:
#include <stdint.h>
static inline uint64_t ror64(uint64_t v, int r) {
return (v >> r) | (v << (64 - r));
}
uint64_t rrxmrrxmsx_0(uint64_t v) {
v ^= ror64(v, 25) ^ ror64(v, 50);
v *= 0xA24BAED4963EE407UL;
v ^= ror64(v, 24) ^ ror64(v, 49);
v *= 0x9FB21C651E98DF25UL;
return v ^ v >> 28;
}
นอกจากนี้ Pelle ยังมีการวิเคราะห์เชิงลึกของมิกเซอร์ 64 บิตที่ใช้ในขั้นตอนสุดท้ายMurmurHash3
และตัวแปรล่าสุด