Skip to content

Commit

Permalink
Fix double hashing hash table infinite loop bug (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
moorara authored Dec 22, 2024
1 parent 87ae660 commit 62d0608
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 13 deletions.
52 changes: 39 additions & 13 deletions symboltable/double_hash_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ func isPrime(n int) bool {
return true
}

// gcd computes the greatest common divisor of two numbers.
// It implements the Euclidean algorithm.
func gcd(a, b uint64) uint64 {
// Ensure a ≥ b
a, b = max(a, b), min(a, b)

/*
* Let the quotient be q and the remainder be r, so that a = b × q + r
* Replace a with b and b with r
* Repeat this until the remainder r becomes 0
* The GCD is the last non-zero remainder
*/

for b != 0 {
a, b = b, a%b
}

return a
}

// doubleHashTable is a hash table with double hashing for conflict resolution.
type doubleHashTable[K, V any] struct {
entries []*hashTableEntry[K, V]
Expand Down Expand Up @@ -129,23 +149,29 @@ func (ht *doubleHashTable[K, V]) probe(key K) func() int {
// M must be a power of 2
M, P := uint64(ht.m), uint64(ht.p)
h1 := h & (M - 1) // [0, M-1]
var h2 uint64

var i, next uint64
var h2, i, next uint64

return func() int {
switch i {
case 0:
if i == 0 {
next = h1
case 1:
// The secondary hash value, h₂(k), must satisfy the following properties:
// 1. h₂(k) ≠ 0: Ensures progress in probing and prevents infinite loops.
// 2. h₂(k) and m are relatively prime: Guarantees the entire table is cycled through for any key.
// 3. Independence from the primary hash, h₁(k): Avoids overlapping collision paths.
// 4. Efficient computation: Suitable for high-performance hash table operations.
h2 = P - (h % P) // [1, p] (p < m)
next = (h1 + h2) % M
default:
} else {
if i == 1 {
// The secondary hash value, h₂(k), must satisfy the following properties:
// 1. h₂(k) ≠ 0: Ensures progress in probing and prevents infinite loops.
// 2. h₂(k) and m are coprime: Guarantees the entire table is cycled through for any key.
// 3. Independence from the primary hash, h₁(k): Avoids overlapping collision paths.
// 4. Efficient computation: Suitable for high-performance hash table operations.
h2 = P - (h % P) // [1, p] (p < m)

// The hash table size (M) and the secondary hash must be relatively prime.
// If they share a common divisor greater than 1, the sequence generated by (h1 + i*h2) % M
// will only cover a subset of the indices in [0, M-1] and an infinite loop occurs.
if gcd(M, h2) != 1 {
h2++
}
}

next = (h1 + i*h2) % M
}

Expand Down
88 changes: 88 additions & 0 deletions symboltable/double_hash_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

. "github.com/moorara/algo/generic"
. "github.com/moorara/algo/hash"
"github.com/stretchr/testify/assert"
)

func getDoubleHashTableTests() []symbolTableTest[string, int] {
Expand Down Expand Up @@ -87,6 +88,93 @@ func getDoubleHashTableTests() []symbolTableTest[string, int] {
return tests
}

func TestIsPrime(t *testing.T) {
tests := []struct {
name string
n int
expectedIsPrime bool
}{
{
name: "Negative",
n: -1,
expectedIsPrime: false,
},
{
name: "Zero",
n: 0,
expectedIsPrime: false,
},
{
name: "One",
n: 1,
expectedIsPrime: false,
},
{
name: "PrimeLessThan10",
n: 7,
expectedIsPrime: true,
},
{
name: "NotPrimeLessThan10",
n: 8,
expectedIsPrime: false,
},
{
name: "PrimeLessThan100",
n: 97,
expectedIsPrime: true,
},
{
name: "NotPrimeLessThan100",
n: 64,
expectedIsPrime: false,
},
{
name: "PrimeLessThan1000",
n: 997,
expectedIsPrime: true,
},
{
name: "NotPrimeLessThan1000",
n: 666,
expectedIsPrime: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedIsPrime, isPrime(tc.n))
})
}
}

func TestGCD(t *testing.T) {
tests := []struct {
name string
a, b uint64
expectedGCD uint64
}{
{
name: "GCDOne",
a: 64,
b: 61,
expectedGCD: 1,
},
{
name: "GCDGreaterThanOne",
a: 48,
b: 64,
expectedGCD: 16,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedGCD, gcd(tc.a, tc.b))
})
}
}

func TestDoubleHashTable(t *testing.T) {
tests := getDoubleHashTableTests()

Expand Down

0 comments on commit 62d0608

Please sign in to comment.