hashtable 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +39 -126
- data/lib/hashtable/hash_table.rb +130 -76
- data/lib/hashtable/sparse_bit_array.rb +46 -26
- data/lib/hashtable/traits.rb +37 -56
- data/lib/hashtable/version.rb +1 -1
- data/lib/hashtable.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a2d080d4eda5fb41e97c4661131fe7b3124530d4cb3604e907ca1294b38fe20
|
4
|
+
data.tar.gz: 456c01089c6b9764016386137ef4be6ac247fbef83921861d803659c798d9dec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89feb259b63fc1795ff796a0cc434dfe78108aad338a756be4e331034103c2ca3096f617130e4d1d85a24ec7921a7f8d118a08e512b3eae9cd7b4cef8728e690
|
7
|
+
data.tar.gz: a442d8711dcfa1db95600bd48166e35b12f5612f3b94fbe80d08b10a3ea00aaff49002e0f238eaa1273b456a170f9ee8722b9eb79b03934d686271c71ff32274
|
data/README.md
CHANGED
@@ -52,116 +52,57 @@ end
|
|
52
52
|
Store numbers:
|
53
53
|
|
54
54
|
```ruby
|
55
|
-
class Traits
|
56
|
-
def hash_lookup_key(key)
|
57
|
-
key
|
58
|
-
end
|
59
|
-
|
60
|
-
def lookup_key_to_storage_key(key)
|
61
|
-
key
|
62
|
-
end
|
63
|
-
|
64
|
-
def storage_key_to_lookup_key(key)
|
65
|
-
key
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
55
|
table = HashTable::HashTable.new(2)
|
70
|
-
|
71
|
-
table.set(
|
72
|
-
table.set(
|
73
|
-
table.set(
|
74
|
-
table.set(
|
75
|
-
table.set(
|
76
|
-
table.
|
56
|
+
table.set(3, 7, HashTable::IdentityHashTraits.new)
|
57
|
+
table.set(4, 5, HashTable::IdentityHashTraits.new)
|
58
|
+
table.set(5, 6, HashTable::IdentityHashTraits.new)
|
59
|
+
table.set(6, 7, HashTable::IdentityHashTraits.new)
|
60
|
+
table.set(8, 9, HashTable::IdentityHashTraits.new)
|
61
|
+
table.set(9, 19, HashTable::IdentityHashTraits.new)
|
62
|
+
expect(table.size).to eq(6)
|
63
|
+
expect(table.get(3, HashTable::IdentityHashTraits.new)).to eq(7)
|
64
|
+
expect(table.get(4, HashTable::IdentityHashTraits.new)).to eq(5)
|
65
|
+
expect(table.get(5, HashTable::IdentityHashTraits.new)).to eq(6)
|
66
|
+
expect(table.get(6, HashTable::IdentityHashTraits.new)).to eq(7)
|
67
|
+
expect(table.get(8, HashTable::IdentityHashTraits.new)).to eq(9)
|
68
|
+
expect(table.get(9, HashTable::IdentityHashTraits.new)).to eq(19)
|
69
|
+
expect(table.capacity).to eq(16)
|
77
70
|
```
|
78
71
|
|
79
72
|
Store strings:
|
80
73
|
|
81
74
|
```ruby
|
82
|
-
class StringTraits
|
83
|
-
attr_reader :string_table, :string_index
|
84
|
-
|
85
|
-
def initialize
|
86
|
-
@string_table = "\0"
|
87
|
-
@string_index = 1
|
88
|
-
end
|
89
|
-
|
90
|
-
def hash_lookup_key(key)
|
91
|
-
result = 0
|
92
|
-
key.each_byte { |byte| result += byte * 13 }
|
93
|
-
result
|
94
|
-
end
|
95
|
-
|
96
|
-
def lookup_key_to_storage_key(key)
|
97
|
-
@string_table += "#{key}\0"
|
98
|
-
old_si = @string_index
|
99
|
-
@string_index += key.length + 1
|
100
|
-
old_si
|
101
|
-
end
|
102
|
-
|
103
|
-
def storage_key_to_lookup_key(offset)
|
104
|
-
@string_table[offset..-1][/[^\0]+/]
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
|
109
75
|
table = HashTable::HashTable.new(2)
|
110
|
-
traits =
|
76
|
+
traits = HashTable::StringIdentityHashTraits.new
|
111
77
|
table.set('ViewController64.h', 'ViewController64.h', traits)
|
112
78
|
table.set('ViewController65.h', 'ViewController65.h', traits)
|
113
79
|
table.set('ViewController66.h', 'ViewController66.h', traits)
|
114
80
|
table.set('ViewController67.h', 'ViewController67.h', traits)
|
115
81
|
table.set('ViewController68.h', 'ViewController68.h', traits)
|
116
82
|
table.set('ViewController69.h', 'ViewController69.h', traits)
|
117
|
-
|
118
|
-
table.get('ViewController64.h', traits)
|
119
|
-
|
120
|
-
|
83
|
+
expect(table.size).to eq(6)
|
84
|
+
expect(table.get('ViewController64.h', traits)).to eq('ViewController64.h')
|
85
|
+
expect(table.get('ViewController65.h', traits)).to eq('ViewController65.h')
|
86
|
+
expect(table.get('ViewController66.h', traits)).to eq('ViewController66.h')
|
87
|
+
expect(table.get('ViewController67.h', traits)).to eq('ViewController67.h')
|
88
|
+
expect(table.get('ViewController68.h', traits)).to eq('ViewController68.h')
|
89
|
+
expect(table.get('ViewController69.h', traits)).to eq('ViewController69.h')
|
90
|
+
expect(table.capacity).to eq(16)
|
91
|
+
expect(traits.string_table).to eq("\u0000ViewController64.h\u0000ViewController65.h\u0000ViewController66.h\u0000ViewController67.h\u0000ViewController68.h\u0000ViewController69.h\u0000")
|
121
92
|
```
|
122
93
|
|
123
94
|
Store just key string:
|
124
95
|
|
125
96
|
```ruby
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
def initialize
|
130
|
-
@string_table = "\0"
|
131
|
-
@string_index = 1
|
132
|
-
end
|
133
|
-
|
134
|
-
def hash_lookup_key(key)
|
135
|
-
result = 0
|
136
|
-
key.each_byte { |byte| result += byte * 13 }
|
137
|
-
result
|
138
|
-
end
|
139
|
-
|
140
|
-
def lookup_key_to_storage_key(key)
|
141
|
-
@string_table += "#{key}\0"
|
142
|
-
old_si = @string_index
|
143
|
-
@string_index += key.length + 1
|
144
|
-
old_si
|
145
|
-
end
|
146
|
-
|
147
|
-
def storage_key_to_lookup_key(offset)
|
148
|
-
@string_table[offset..-1][/[^\0]+/]
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
|
153
|
-
table = HashTable::HashTable.new(2)
|
154
|
-
traits = StringTraits.new
|
155
|
-
|
97
|
+
table = HashTable::HashTable.new
|
98
|
+
traits = HashTable::StringIdentityHashTraits.new
|
156
99
|
(0..31).each do |i|
|
157
|
-
|
100
|
+
table.add("ViewController#{i}.h", traits)
|
158
101
|
end
|
159
|
-
#
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
# 32
|
164
|
-
p table.num_entries
|
102
|
+
p "#{table.size}---#{table.capacity}----#{table.num_entries}"
|
103
|
+
expect(table.size).to eq(32)
|
104
|
+
expect(table.capacity).to eq(64)
|
105
|
+
expect(table.num_entries).to eq(32)
|
165
106
|
```
|
166
107
|
|
167
108
|
Store strings and expand capacity:
|
@@ -171,44 +112,16 @@ Store strings and expand capacity:
|
|
171
112
|
- capacity is power_of_two
|
172
113
|
|
173
114
|
```ruby
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
def initialize
|
178
|
-
@string_table = "\0"
|
179
|
-
@string_index = 1
|
180
|
-
end
|
181
|
-
|
182
|
-
def hash_lookup_key(key)
|
183
|
-
result = 0
|
184
|
-
key.each_byte { |byte| result += byte * 13 }
|
185
|
-
result
|
186
|
-
end
|
187
|
-
|
188
|
-
def lookup_key_to_storage_key(key)
|
189
|
-
@string_table += "#{key}\0"
|
190
|
-
old_si = @string_index
|
191
|
-
@string_index += key.length + 1
|
192
|
-
old_si
|
193
|
-
end
|
194
|
-
|
195
|
-
def storage_key_to_lookup_key(offset)
|
196
|
-
@string_table[offset..-1][/[^\0]+/]
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
table = HashTable::HashTable.new(8192, expand: true)
|
201
|
-
traits = StringTraits.new
|
115
|
+
table = HashTable::HashTable.new(1364, expand: true)
|
116
|
+
traits = HashTable::StringHashTraits.new
|
202
117
|
buckets = (0..1363).map do |i|
|
203
|
-
|
204
|
-
|
118
|
+
table.set("ViewController#{i}.h",
|
119
|
+
["/Users/ws/Desktop/llvm/TestAndTestApp/TestAndTestApp/Group/h2/#{i}", "ViewController#{i}.h"], traits)
|
205
120
|
end
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
# 5461
|
211
|
-
p table.num_entries
|
121
|
+
p "#{table.size}---#{table.capacity}----#{table.num_entries}"
|
122
|
+
expect(table.size).to eq(1364)
|
123
|
+
expect(table.capacity).to eq(8192)
|
124
|
+
expect(table.num_entries).to eq(4097)
|
212
125
|
```
|
213
126
|
|
214
127
|
## Contributing
|
data/lib/hashtable/hash_table.rb
CHANGED
@@ -1,24 +1,91 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'benchmark'
|
4
|
+
|
3
5
|
# module HashTable
|
4
6
|
module HashTable
|
7
|
+
|
8
|
+
EXPAND_KEY_COUNT_CONST = {
|
9
|
+
8 => [0, 0],
|
10
|
+
16 => [6, 9],
|
11
|
+
32 => [8, 17],
|
12
|
+
64 => [13, 33],
|
13
|
+
128 => [23, 65],
|
14
|
+
256 => [44, 129],
|
15
|
+
512 => [86, 257],
|
16
|
+
1024 => [171, 513],
|
17
|
+
2048 => [341, 1025],
|
18
|
+
4096 => [682, 2049],
|
19
|
+
8192 => [1364, 4097],
|
20
|
+
16_384 => [2729, 8193],
|
21
|
+
32_768 => [5459, 16_385],
|
22
|
+
65_536 => [10_920, 32_769],
|
23
|
+
131_072 => [21_842, 65_537],
|
24
|
+
262_144 => [43_687, 131_073],
|
25
|
+
524_288 => [87_377, 262_145],
|
26
|
+
1_048_576 => [174_758, 524_289],
|
27
|
+
2_097_152 => [349_520, 1_048_577],
|
28
|
+
4_194_304 => [699_045, 2_097_153],
|
29
|
+
8_388_608 => [1_398_095, 4_194_305],
|
30
|
+
16_777_216 => [2_796_196, 8_388_609],
|
31
|
+
33_554_432 => [5_592_398, 16_777_217],
|
32
|
+
67_108_864 => [11_184_803, 33_554_433],
|
33
|
+
134_217_728 => [22_369_613, 67_108_865],
|
34
|
+
268_435_456 => [44_739_234, 134_217_729],
|
35
|
+
536_870_912 => [89_478_476, 268_435_457],
|
36
|
+
1_073_741_824 => [178_956_961, 536_870_913],
|
37
|
+
2_147_483_648 => [357_913_931, 1_073_741_825],
|
38
|
+
4_294_967_296 => [715_827_872, 2_147_483_649],
|
39
|
+
8_589_934_592 => [1_431_655_754, 4_294_967_297],
|
40
|
+
17_179_869_184 => [2_863_311_519, 8_589_934_593],
|
41
|
+
34_359_738_368 => [5_726_623_049, 17_179_869_185],
|
42
|
+
68_719_476_736 => [11_453_246_110, 34_359_738_369],
|
43
|
+
137_438_953_472 => [22_906_492_232, 68_719_476_737],
|
44
|
+
274_877_906_944 => [45_812_984_477, 137_438_953_473],
|
45
|
+
549_755_813_888 => [91_625_968_967, 274_877_906_945],
|
46
|
+
1_099_511_627_776 => [183_251_937_948, 549_755_813_889]
|
47
|
+
}.freeze
|
5
48
|
# HashTable
|
6
49
|
# @abstract
|
7
50
|
class HashTable
|
8
|
-
|
9
|
-
|
51
|
+
attr_reader :size
|
52
|
+
|
53
|
+
# @return [HashTable]
|
54
|
+
# @param [object] p_value placeholder value with @buckets_v
|
55
|
+
# @param [Bool] Does it need to be expanded
|
56
|
+
def self.new_from_vlaue_placeholder(count = 0, p_value = nil, expand: false)
|
57
|
+
new(count, p_value: p_value, expand: expand)
|
58
|
+
end
|
10
59
|
|
11
|
-
|
12
|
-
|
13
|
-
|
60
|
+
# @return [HashTable]
|
61
|
+
# @param [Integer] count of entries
|
62
|
+
# @param [object] p_key placeholder key with @buckets_k
|
63
|
+
# @param [object] p_value placeholder value with @buckets_v
|
64
|
+
# @param [Bool] Does it need to be expanded
|
65
|
+
def initialize(count = 0, p_key: nil, p_value: nil, expand: false)
|
66
|
+
capacity = calculate_count(count, expand: expand)
|
67
|
+
@buckets_k = Array.new(capacity, p_key)
|
68
|
+
@buckets_v = Array.new(capacity, p_value)
|
14
69
|
@present = SparseBitArray.new
|
15
70
|
@deleted = SparseBitArray.new
|
16
71
|
@expand = expand
|
17
|
-
@
|
72
|
+
@size = 0
|
18
73
|
end
|
19
74
|
|
20
|
-
def
|
21
|
-
|
75
|
+
def num_entries
|
76
|
+
raise 'count must less than 2_863_311_519' if size > 2_863_311_519
|
77
|
+
return size unless @expand
|
78
|
+
|
79
|
+
es = EXPAND_KEY_COUNT_CONST[capacity]
|
80
|
+
size - es[0] + es[1]
|
81
|
+
end
|
82
|
+
|
83
|
+
def keys
|
84
|
+
@buckets_k
|
85
|
+
end
|
86
|
+
|
87
|
+
def values
|
88
|
+
@buckets_v
|
22
89
|
end
|
23
90
|
|
24
91
|
def empty?
|
@@ -26,36 +93,27 @@ module HashTable
|
|
26
93
|
end
|
27
94
|
|
28
95
|
def capacity
|
29
|
-
@
|
96
|
+
@buckets_k.length
|
30
97
|
end
|
31
98
|
|
32
99
|
# @return [Integer] Find the entry whose key has the specified hash value,
|
33
100
|
# using the specified traits defining hash function and equality.
|
34
101
|
# @param [object] key
|
35
102
|
# @param [object] traits
|
36
|
-
def find(key, traits)
|
37
|
-
|
38
|
-
|
39
|
-
unless traits.respond_to?(:storage_key_to_lookup_key)
|
40
|
-
raise ArgumentError,
|
41
|
-
'traits must respond to storage_key_to_lookup_key method'
|
42
|
-
end
|
43
|
-
h = traits.hash_lookup_key(key) % capacity
|
103
|
+
def find(key, traits = IdentityHashTraits.new)
|
104
|
+
nums = capacity - 1
|
105
|
+
h = traits.hash_lookup_key(key) & nums
|
44
106
|
i = h
|
45
|
-
fisrt_unsed = nil
|
46
107
|
loop do
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
i = (i + 1) % capacity
|
108
|
+
bucket = @buckets_k[i]
|
109
|
+
return i if !bucket.nil? && present?(i) && traits.storage_key_to_lookup_key(bucket) == key
|
110
|
+
|
111
|
+
break unless deleted?(i)
|
112
|
+
|
113
|
+
i = (i + 1) & nums
|
54
114
|
break if i == h
|
55
115
|
end
|
56
|
-
|
57
|
-
|
58
|
-
fisrt_unsed
|
116
|
+
i
|
59
117
|
end
|
60
118
|
|
61
119
|
# @return [Integer] internal key
|
@@ -63,23 +121,20 @@ module HashTable
|
|
63
121
|
# @param [object] key
|
64
122
|
# @param [object] value
|
65
123
|
# @param [object] traits
|
66
|
-
def set(key, value, traits)
|
124
|
+
def set(key, value, traits = IdentityHashTraits.new)
|
67
125
|
set_as_interal(key, traits, value)
|
68
126
|
end
|
69
127
|
|
70
|
-
def get(key, traits)
|
128
|
+
def get(key, traits = IdentityHashTraits.new)
|
71
129
|
i = find(key, traits)
|
72
|
-
|
73
|
-
raise ArgumentError if bucket.nil? || bucket.empty?
|
74
|
-
|
75
|
-
bucket.last
|
130
|
+
@buckets_v[i]
|
76
131
|
end
|
77
132
|
|
78
133
|
# @return [Integer] internal key
|
79
134
|
# Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
|
80
135
|
# @param [object] key
|
81
136
|
# @param [object] traits
|
82
|
-
def add(key, traits)
|
137
|
+
def add(key, traits = IdentityHashTraits.new)
|
83
138
|
set_as_interal(key, traits)
|
84
139
|
end
|
85
140
|
|
@@ -87,13 +142,13 @@ module HashTable
|
|
87
142
|
# Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
|
88
143
|
# @param [object] key
|
89
144
|
# @param [object] traits
|
90
|
-
def adds(keys, traits)
|
145
|
+
def adds(keys, traits = IdentityHashTraits.new)
|
91
146
|
keys.map { |key| set_as_interal(key, traits) }
|
92
147
|
end
|
93
148
|
|
94
149
|
protected
|
95
150
|
|
96
|
-
attr_reader :present, :deleted, :
|
151
|
+
attr_reader :present, :deleted, :buckets_k, :buckets_v
|
97
152
|
|
98
153
|
def present?(key)
|
99
154
|
@present.test?(key)
|
@@ -109,62 +164,61 @@ module HashTable
|
|
109
164
|
# @param [object] traits
|
110
165
|
# @param [object] value
|
111
166
|
# @param [object] internal_key
|
112
|
-
def set_as_interal(key, traits, value = nil, internal_key = nil)
|
113
|
-
unless traits.respond_to?(:lookup_key_to_storage_key)
|
114
|
-
raise ArgumentError,
|
115
|
-
'traits must respond to lookup_key_to_storage_key method'
|
116
|
-
end
|
117
|
-
unless traits.respond_to?(:storage_key_to_lookup_key)
|
118
|
-
raise ArgumentError,
|
119
|
-
'traits must respond to storage_key_to_lookup_key method'
|
120
|
-
end
|
121
|
-
|
167
|
+
def set_as_interal(key, traits = IdentityHashTraits.new, value = nil, internal_key = nil)
|
122
168
|
index = find(key, traits)
|
123
|
-
|
124
|
-
if
|
169
|
+
bucket_k = @buckets_k[index]
|
170
|
+
if bucket_k.nil?
|
125
171
|
raise ArgumentError if present?(index)
|
126
172
|
|
127
|
-
|
128
|
-
|
173
|
+
@buckets_k[index] = bucket_k = internal_key.nil? ? traits.lookup_key_to_storage_key(key) : internal_key
|
174
|
+
@buckets_v[index] = traits.lookup_key_to_storage_value(bucket_k, value)
|
129
175
|
@present.set(index)
|
130
176
|
@deleted.reset(index)
|
177
|
+
@size += 1
|
131
178
|
grow(traits)
|
132
179
|
else
|
133
|
-
raise ArgumentError unless present?(index)
|
134
|
-
raise ArgumentError unless traits.storage_key_to_lookup_key(@buckets[index].first) == key
|
180
|
+
raise ArgumentError unless present?(index) || traits.storage_key_to_lookup_key(bucket_k) == key
|
135
181
|
|
136
|
-
|
182
|
+
@buckets_v[index] = traits.lookup_key_to_storage_value(bucket_k, value)
|
137
183
|
end
|
138
|
-
|
139
|
-
|
140
|
-
bucket[0]
|
184
|
+
bucket_k
|
141
185
|
end
|
142
186
|
|
143
187
|
private
|
144
188
|
|
145
|
-
def
|
146
|
-
|
147
|
-
|
148
|
-
|
189
|
+
def need_grow(size, count, expand: false)
|
190
|
+
raise 'count must less than 2_863_311_519' if count > 2_863_311_519
|
191
|
+
|
192
|
+
count *= 2
|
193
|
+
return size >= count / 3 + 1 unless expand
|
194
|
+
|
195
|
+
size >= EXPAND_KEY_COUNT_CONST[count][0]
|
196
|
+
end
|
197
|
+
|
198
|
+
def calculate_count(size, expand: false)
|
199
|
+
return 8 if size < 6
|
200
|
+
raise 'count must less than 2_863_311_519' if size > 2_863_311_519
|
201
|
+
|
202
|
+
if expand
|
203
|
+
EXPAND_KEY_COUNT_CONST.each_pair { |key, e| return key / 2 if size < e[0] }
|
204
|
+
else
|
205
|
+
2**(size * 3 / 2 + 1 - 1).bit_length
|
149
206
|
end
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
@num_entries = m + 1 if @expand
|
158
|
-
new_capacity = m * 2
|
159
|
-
return if new_capacity <= capacity
|
160
|
-
|
161
|
-
new_map = HashTable.new(new_capacity, expand: @expand)
|
207
|
+
end
|
208
|
+
|
209
|
+
def grow(traits = IdentityHashTraits.new)
|
210
|
+
return unless need_grow(size, capacity, expand: @expand)
|
211
|
+
|
212
|
+
new_map = HashTable.new(size, expand: @expand)
|
162
213
|
@present.each do |i|
|
163
|
-
|
214
|
+
key = @buckets_k[i]
|
215
|
+
lookup_key = traits.storage_key_to_lookup_key(key)
|
164
216
|
# Private methods cannot be called with an explicit receiver and protected ones can.
|
165
|
-
new_map.set_as_interal(lookup_key, traits, @
|
217
|
+
new_map.set_as_interal(lookup_key, traits, @buckets_v[i], key)
|
166
218
|
end
|
167
|
-
|
219
|
+
|
220
|
+
@buckets_k = new_map.buckets_k
|
221
|
+
@buckets_v = new_map.buckets_v
|
168
222
|
@present = new_map.present
|
169
223
|
@deleted = new_map.deleted
|
170
224
|
end
|
@@ -30,11 +30,18 @@ module HashTable
|
|
30
30
|
@bits[index]
|
31
31
|
end
|
32
32
|
|
33
|
+
def ==(other)
|
34
|
+
return false unless index == other.index
|
35
|
+
|
36
|
+
(0...BITWORDS_PER_ELEMENT).each do |i|
|
37
|
+
return false unless @bits[i] == other.bits[i]
|
38
|
+
end || true
|
39
|
+
end
|
40
|
+
|
33
41
|
def empty?
|
34
42
|
(0...BITWORDS_PER_ELEMENT).each do |i|
|
35
43
|
return false unless @bits[i].zero?
|
36
|
-
end
|
37
|
-
true
|
44
|
+
end || true
|
38
45
|
end
|
39
46
|
|
40
47
|
def set(index)
|
@@ -48,9 +55,8 @@ module HashTable
|
|
48
55
|
def test_and_set?(index)
|
49
56
|
unless test(index)
|
50
57
|
set(Idx)
|
51
|
-
|
52
|
-
end
|
53
|
-
false
|
58
|
+
true
|
59
|
+
end || false
|
54
60
|
end
|
55
61
|
|
56
62
|
def reset(index)
|
@@ -64,8 +70,13 @@ module HashTable
|
|
64
70
|
# v = (v * 0x0101010101010101) & UINT_64_MAX
|
65
71
|
# v >>= 56
|
66
72
|
def count
|
67
|
-
|
68
|
-
v =
|
73
|
+
@bits.inject(0) do |nums, b|
|
74
|
+
v = b
|
75
|
+
v -= ((v >> 1) & 0x5555555555555555)
|
76
|
+
v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
|
77
|
+
v = (v + (v >> 4) & 0x0F0F0F0F0F0F0F0F)
|
78
|
+
v = (v * 0x0101010101010101) & UINT_64_MAX
|
79
|
+
v >>= 56
|
69
80
|
nums + v
|
70
81
|
end
|
71
82
|
end
|
@@ -74,11 +85,12 @@ module HashTable
|
|
74
85
|
def first
|
75
86
|
(0...BITWORDS_PER_ELEMENT).each do |i|
|
76
87
|
v = @bits[i]
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
88
|
+
unless v.zero?
|
89
|
+
count = v.digits(2).index(1)
|
90
|
+
return i * BITWORD_SIZE + count
|
91
|
+
end
|
81
92
|
end
|
93
|
+
raise ArgumentError, 'Illegal empty element'
|
82
94
|
end
|
83
95
|
|
84
96
|
# @return [Integer] the index of the last set bit
|
@@ -86,11 +98,12 @@ module HashTable
|
|
86
98
|
(0...BITWORDS_PER_ELEMENT).each do |i|
|
87
99
|
index = BITWORDS_PER_ELEMENT - i - 1
|
88
100
|
v = @bits[index]
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
101
|
+
unless v.zero?
|
102
|
+
count = BIT_WORD - v.bit_length
|
103
|
+
return index * BITWORD_SIZE + BITWORD_SIZE - count - 1
|
104
|
+
end
|
93
105
|
end
|
106
|
+
raise ArgumentError, 'Illegal empty element'
|
94
107
|
end
|
95
108
|
|
96
109
|
# @return [Integer] the index of the next set bit starting from the "index" bit.
|
@@ -153,6 +166,10 @@ module HashTable
|
|
153
166
|
bits >>= 1
|
154
167
|
end
|
155
168
|
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
attr_reader :bits
|
156
173
|
end
|
157
174
|
|
158
175
|
# SparseBitArray is an implementation of a bitmap that is sparse by only storing the elements that have non-zero bits set.
|
@@ -163,6 +180,7 @@ module HashTable
|
|
163
180
|
# @return [Integer] Pointer to our current Element.
|
164
181
|
# This has no visible effect on the external state of a SparseBitArray
|
165
182
|
# It's just used to improve performance in the common case of testing/modifying bits with similar indices.
|
183
|
+
# attr_reader :current_index
|
166
184
|
attr_reader :current_index
|
167
185
|
|
168
186
|
def initialize
|
@@ -177,8 +195,7 @@ module HashTable
|
|
177
195
|
|
178
196
|
e_index = index / ELEMENT_SIZE
|
179
197
|
element_i = lower_bound(e_index)
|
180
|
-
|
181
|
-
return false if element_i == last || elements[element_i].index != e_index
|
198
|
+
return false if element_i == elements.length || elements[element_i].index != e_index
|
182
199
|
|
183
200
|
elements[element_i].test?(index % ELEMENT_SIZE)
|
184
201
|
end
|
@@ -205,13 +222,14 @@ module HashTable
|
|
205
222
|
e_index = index / ELEMENT_SIZE
|
206
223
|
element_i = lower_bound(e_index)
|
207
224
|
new_e = elements[element_i]
|
208
|
-
unless new_e.nil?
|
209
|
-
|
210
|
-
|
225
|
+
eql = new_e.index <=> e_index unless new_e.nil?
|
226
|
+
if eql.nil? || eql != 0
|
227
|
+
element_i += 1 if eql == -1
|
228
|
+
new_e = SparseBitArrayElement.new(e_index)
|
229
|
+
@elements.insert(element_i, new_e)
|
211
230
|
end
|
212
231
|
@current_index = element_i
|
213
|
-
|
214
|
-
@elements[@current_index].set(index % ELEMENT_SIZE)
|
232
|
+
new_e.set(index % ELEMENT_SIZE)
|
215
233
|
end
|
216
234
|
|
217
235
|
# @return [Void] Test, Set a bit in the bitmap
|
@@ -269,12 +287,14 @@ module HashTable
|
|
269
287
|
@current_index -= 1 if @current_index == elements.length
|
270
288
|
element_i = @current_index
|
271
289
|
element = @elements[element_i]
|
272
|
-
return element_i if element.index == index
|
273
290
|
|
274
|
-
@current_index =
|
291
|
+
@current_index = case element.index <=> index
|
292
|
+
when 0
|
293
|
+
element_i
|
294
|
+
when 1
|
275
295
|
@elements[0..element_i].rindex { |e| e.index <= index } || 0
|
276
|
-
|
277
|
-
@elements[element_i..].
|
296
|
+
when -1
|
297
|
+
@elements[element_i..].count { |e| e.index < index } + element_i
|
278
298
|
end
|
279
299
|
end
|
280
300
|
end
|
data/lib/hashtable/traits.rb
CHANGED
@@ -1,84 +1,65 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: false
|
2
2
|
|
3
3
|
# module HashTable
|
4
4
|
module HashTable
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
key
|
12
|
-
end
|
13
|
-
|
14
|
-
def storage_key_to_lookup_key(key)
|
15
|
-
key
|
16
|
-
end
|
5
|
+
# IdentityHashTraits
|
6
|
+
class IdentityHashTraits
|
7
|
+
define_method(:hash_lookup_key) { |key| key }
|
8
|
+
define_method(:lookup_key_to_storage_key) { |key| key }
|
9
|
+
define_method(:storage_key_to_lookup_key) { |key| key }
|
10
|
+
define_method(:lookup_key_to_storage_value) { |_key, value| value }
|
17
11
|
end
|
18
12
|
|
19
|
-
|
20
|
-
|
13
|
+
# StringIdentityHashTraits
|
14
|
+
class StringIdentityHashTraits < IdentityHashTraits
|
15
|
+
attr_reader :string_table, :buckets
|
21
16
|
|
22
|
-
def initialize
|
17
|
+
def initialize(&block)
|
18
|
+
super
|
23
19
|
@string_table = "\0"
|
24
|
-
@
|
20
|
+
@buckets = {}
|
21
|
+
@indexs = {}
|
25
22
|
end
|
26
23
|
|
27
24
|
def hash_lookup_key(key)
|
28
|
-
|
29
|
-
key.each_byte { |byte| result += byte * 13 }
|
30
|
-
result
|
25
|
+
key.downcase.bytes.inject(:+) * 13
|
31
26
|
end
|
32
27
|
|
33
28
|
def lookup_key_to_storage_key(key)
|
34
|
-
@
|
35
|
-
|
36
|
-
|
29
|
+
return @buckets[key] unless @buckets[key].nil?
|
30
|
+
|
31
|
+
old_si = @string_table.length
|
32
|
+
@buckets[key] = old_si
|
33
|
+
@string_table << "#{key}\0".b
|
34
|
+
@indexs[old_si] = key
|
37
35
|
old_si
|
38
36
|
end
|
39
37
|
|
40
38
|
def storage_key_to_lookup_key(offset)
|
41
|
-
@
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class StringTraits2
|
46
|
-
attr_reader :string_table, :buckets, :hash_h
|
39
|
+
return @indexs[offset] unless @indexs[offset].nil?
|
47
40
|
|
48
|
-
|
49
|
-
@
|
50
|
-
|
51
|
-
@buckets = {}
|
52
|
-
@hash_h = {}
|
41
|
+
key = @string_table[offset..][/[^\0]+/]
|
42
|
+
@indexs[offset] = key
|
43
|
+
key
|
53
44
|
end
|
45
|
+
end
|
54
46
|
|
55
|
-
|
56
|
-
|
47
|
+
# StringHashTraits
|
48
|
+
class StringHashTraits < StringIdentityHashTraits
|
49
|
+
attr_reader :string_table, :buckets
|
57
50
|
|
58
|
-
|
59
|
-
|
60
|
-
@
|
61
|
-
result
|
51
|
+
def initialize(&block)
|
52
|
+
super
|
53
|
+
@block = block
|
62
54
|
end
|
63
55
|
|
64
|
-
def
|
65
|
-
return
|
56
|
+
def lookup_key_to_storage_value(key, values)
|
57
|
+
return if values.nil? || values.empty?
|
66
58
|
|
67
|
-
|
68
|
-
|
69
|
-
@string_index += key.length + 1
|
70
|
-
@buckets[key] = old_si
|
71
|
-
old_si
|
72
|
-
end
|
59
|
+
bs = values.map { |v| lookup_key_to_storage_key(v) }.unshift(key)
|
60
|
+
return bs if @block.nil?
|
73
61
|
|
74
|
-
|
75
|
-
value.inject([@buckets[key]]) do |sum, v|
|
76
|
-
sum << lookup_key_to_storage_key(v)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def storage_key_to_lookup_key(offset)
|
81
|
-
@string_table[offset..][/[^\0]+/]
|
62
|
+
@block.call(bs)
|
82
63
|
end
|
83
64
|
end
|
84
65
|
end
|
data/lib/hashtable/version.rb
CHANGED
data/lib/hashtable.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashtable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cat1237
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-05-
|
11
|
+
date: 2022-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|