Skip to content

Commit 6b31049

Browse files
Merge pull request #66 from kennethshackleton/feature/reduce-hash-table
Reduce hash table to less than 140kB.
2 parents 627cc56 + c1dad59 commit 6b31049

File tree

7 files changed

+4624
-6497
lines changed

7 files changed

+4624
-6497
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The implementation being immutable is already thread-safe and there is no initia
2323

2424
## How does it work?
2525

26-
We exploit a key-scheme that gives us just enough uniqueness to correctly identify the integral rank of any 7-card hand, where the greater this rank is the better the hand we hold and two hands of the same rank always draw. We require a memory footprint of 230kB and typically six additions to rank a hand.
26+
We exploit a key-scheme that gives us just enough uniqueness to correctly identify the integral rank of any 7-card hand, where the greater this rank is the better the hand we hold and two hands of the same rank always draw. We require a memory footprint just shy of 140kB and typically six additions to rank a hand.
2727

2828
To start with we computed by brute force the first thirteen non-negative integers such that the formal sum of exactly seven with each taken at most four times is unique among all such sums: 0, 1, 5, 22, 98, 453, 2031, 8698, 22854, 83661, 262349, 636345 and 1479181. A valid sum might be 0+0+1+1+1+1+5 = 9 or 0+98+98+453+98+98+1 = 846, but invalid sum expressions include 0+262349+0+0+0+1 (too few summands), 1+1+5+22+98+453+2031+8698 (too many summands), 0+1+5+22+98+453+2031+8698 (again too many summands, although 1+5+22+98+453+2031+8698 is a legitimate expression) and 1+1+1+1+1+98+98 (too many 1's). We assign these integers as the card face values and add these together to generate a key for any non-flush 7-card hand. The largest non-flush key we see is 7825759, corresponding to any of the four quad-of-aces-full-of-kings.
2929

scripts/perfect_hash.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
# A Czech-Havas-Majewski perfect hash implementation.
2424
# See "Fundamental Study Perfect Hashing" by Czech, Havas, Majewski,
2525
# Theoretical Computer Science 182 (1997) 1-143.
26-
#
27-
# Modified by sorting input so as to always write densest rows first.
2826

2927
from rank import rank
3028

@@ -50,54 +48,67 @@
5048
print "Key count is %i." % (len(keys),)
5149
print "Max key is %i." % (max_key,)
5250

53-
side = 128 # Power of 2 to ultimately optimise hash key calculation.
54-
print "Square will be of side %i." % (side,)
51+
M = 31
52+
power = 9
53+
side = (1 << power)
54+
bits = 23
55+
print "Table will be of side %i." % (side,)
5556

56-
square = [[-1]*(512*side) for i in xrange(side)]
57+
NOT_A_VALUE = -1
5758

58-
offset = [0]*((max_key / side) + 1)
59-
ranks = [-1]*max_key
60-
length = 0
59+
square = [[NOT_A_VALUE]*(600*side) for i in xrange(side)]
60+
61+
offset = [0]*(1 << (bits - power))
62+
ranks = [NOT_A_VALUE]*max_key
63+
64+
diffused_keys = {}
6165

62-
counts = [0]*len(offset)
66+
def diffuse(k):
67+
k *= M
68+
return k & ((1 << bits) - 1)
6369

6470
for k in keys:
71+
d = diffuse(k)
72+
assert d not in diffused_keys
73+
diffused_keys[diffuse(k)] = k
74+
75+
for k, v in diffused_keys.iteritems():
6576
r = k / side
66-
square[k % side][r] = rank(k)
67-
counts[r] += 1
77+
assert square[k % side][r] == NOT_A_VALUE
78+
square[k % side][r] = rank(v)
6879

69-
sorted_rows = sorted(
70-
xrange(0, len(offset)),
71-
key=lambda x: counts[x],
72-
reverse=True)
80+
length = 0
7381

7482
for i in xrange(0, len(offset)):
75-
z = sorted_rows[i]
76-
for j in xrange(0, len(ranks)-side):
83+
for j in xrange(0, len(ranks)):
7784
collision = False
7885
for k in xrange(0, side):
79-
s = square[k][z]
86+
s = square[k][i]
8087
h = ranks[j+k]
81-
collision = (s != -1 and h != -1 and s != h)
88+
collision = (s != NOT_A_VALUE and h != NOT_A_VALUE and s != h)
8289
if collision: break
8390
if not collision:
84-
offset[z] = j
91+
offset[i] = j
8592
for k in xrange(0, side):
86-
s = square[k][z]
87-
if s != -1:
93+
s = square[k][i]
94+
if s != NOT_A_VALUE:
8895
n = j+k
8996
ranks[n] = s
9097
length = max(length, n+1)
91-
print "Offset of row %i is %i (length %i)." % (z, j, length)
98+
print "Offset of row %i is %i (length %i)." % (i, j, length)
9299
break
93100

101+
for k in keys:
102+
d = diffuse(k)
103+
assert rank(k) == ranks[offset[d / side] + (d % side)]
104+
94105
for i in xrange(0, length):
95-
if ranks[i] == -1: ranks[i] = 0
106+
if ranks[i] == NOT_A_VALUE: ranks[i] = 0
96107

97-
with open('./ranks_sort_%s' % (side,), 'w') as f:
108+
with open('./ranks_%i_%i' % (side, M,), 'w') as f:
98109
f.write("%s\n" % (ranks[0:length],))
99110

100-
with open('./offset_sort_%s' % (side,), 'w') as f:
111+
with open('./offset_%i_%i' % (side, M,), 'w') as f:
101112
f.write("%s\n" % (offset,))
102113

103-
print "Hash table has length %i." % (length,)
114+
print "Accepted hash table with length %i." % (length,)

scripts/rank.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
FACE_BIT_MASK = ((1 << FLUSH_BIT_SHIFT) - 1)
2727

2828
ranks = [
29-
7220, 7243, 5860, 7297, 7309, 7229, 7242, 7298, 7153, 7310, 7206, 7298, 7142,
29+
7220, 7243, 5860, 7297, 7309, 7229, 7242, 7298, 7153, 7310, 7206, 7298, 7142,
3030
7154, 7310, 7298, 7165, 7166, 7166, 7310, 7321, 7322, 7322, 7322, 7299, 7153,
3131
7311, 7194, 7299, 7141, 7153, 7311, 7299, 7142, 4161, 7154, 7311, 7165, 7165,
3232
7166, 7166, 7205, 7323, 7323, 7323, 7299, 7143, 7155, 7311, 7299, 7143, 4183,

src/Constants.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@
8686
#define MAX_SEVEN_FLUSH_KEY_INT (ACE_FLUSH|KING_FLUSH|QUEEN_FLUSH|JACK_FLUSH|\
8787
TEN_FLUSH|NINE_FLUSH|EIGHT_FLUSH)
8888

89-
#define RANK_OFFSET_SHIFT 7
90-
#define RANK_HASH_MOD 127
89+
#define RANK_OFFSET_SHIFT 9
90+
#define RANK_HASH_MOD ((1<<RANK_OFFSET_SHIFT) - 1)
9191

9292
#define MAX_FLUSH_CHECK_SUM (7*CLUB)
9393

0 commit comments

Comments
 (0)