hashtable 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 84378d52b624dfc1274b36c694b9651af59bbd941ee2cfae71592c48b69b82d8
4
+ data.tar.gz: cf4de36cd314ecc33538e14db09e768fecbcb54eb53546e545cbd40e96e7c8c3
5
+ SHA512:
6
+ metadata.gz: 85562898eb56576dd0f846eea330b66e561acee762071bf08a60ab4e0f683f510b8744b86c40cb61c6401c357fbef9f096e015f1f6aa8143bf040adc24a77dfa
7
+ data.tar.gz: c58e25d6e72d7b5c46de97db5be5d4592bdd50b2a0ac54e921dc79a20e5d0fd156cf46df6e0c68ae4f73a1451feb85e1ab15ff30686adc390a0d7585c8da67e6
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Cat1237
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # Hashtable
2
+ [![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/Cat1237/hashtable/main/LICENSE) 
3
+
4
+ `HashTable` - This provides a hash table data structure that is specialized for handling key/value pairs. This does some funky memory allocation and hashing things to make it extremely efficient, storing the key/value with `SparseBitArray`.
5
+
6
+ `SparseBitArray` - is an implementation of a bitmap that is sparse by only storing the elements that have non-zero bits set.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'hashtable'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle install
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install hashtable
23
+
24
+ ## Usage
25
+
26
+ ### `SparseBitArray`
27
+
28
+ ```ruby
29
+ e = HashTable::SparseBitArrayElement.new
30
+ # Set, Test and Reset a bit in the bitmap
31
+ e.set(23)
32
+ e.test?(17)
33
+ e.reset(4000)
34
+
35
+ # The index of the first set bit
36
+ e.first
37
+ # The index of the last set bit
38
+ e.last
39
+
40
+
41
+ (0..128).to_a.each_index do |i|
42
+ e.set(i)
43
+ end
44
+ # Enumerator
45
+ e.each do |index|
46
+ p "#{index}----"
47
+ end
48
+ ```
49
+
50
+ ### HashTable
51
+
52
+ Store numbers:
53
+
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
+ table = HashTable::HashTable.new(2)
70
+ traits = Traits.new
71
+ table.set(3, 7, )
72
+ table.set(4, 5, traits)
73
+ table.set(5, 6, traits)
74
+ table.set(6, 7, traits)
75
+ table.set(8, 9, traits)
76
+ table.set(9, 19, traits)
77
+ ```
78
+
79
+ Store strings:
80
+
81
+ ```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
+ table = HashTable::HashTable.new(2)
110
+ traits = StringTraits.new
111
+ table.set('ViewController64.h', 'ViewController64.h', traits)
112
+ table.set('ViewController65.h', 'ViewController65.h', traits)
113
+ table.set('ViewController66.h', 'ViewController66.h', traits)
114
+ table.set('ViewController67.h', 'ViewController67.h', traits)
115
+ table.set('ViewController68.h', 'ViewController68.h', traits)
116
+ table.set('ViewController69.h', 'ViewController69.h', traits)
117
+ # ViewController64.h
118
+ table.get('ViewController64.h', traits)
119
+ # \u0000ViewController64.h\u0000ViewController65.h\u0000ViewController66.h\u0000ViewController67.h\u0000ViewController68.h\u0000ViewController69.h\u0000
120
+ p traits.string_table
121
+ ```
122
+
123
+ Store just key string:
124
+
125
+ ```ruby
126
+ class StringTraits
127
+ attr_reader :string_table, :string_index
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
+
156
+ (0..31).each do |i|
157
+ table.add("ViewController#{i}.h", traits)
158
+ end
159
+ # 32
160
+ p table.size
161
+ # 64
162
+ p table.capacity
163
+ # 32
164
+ p table.num_entries
165
+ ```
166
+
167
+ Store strings and expand capacity:
168
+
169
+ - num_entries = capacity + 1
170
+ - capacity = capacity * 2
171
+ - capacity is power_of_two
172
+
173
+ ```ruby
174
+ class StringTraits
175
+ attr_reader :string_table, :string_index
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
202
+ buckets = (0..1363).map do |i|
203
+ a = ["ViewController#{i}.h", "/Users/ws/Desktop/llvm/TestAndTestApp/TestAndTestApp/Group/h2/#{i}", "ViewController#{i}.h"]
204
+ table.adds(a, traits)
205
+ end
206
+ # 2728
207
+ p table.size
208
+ # 8192
209
+ p table.capacity
210
+ # 5461
211
+ p table.num_entries
212
+ ```
213
+
214
+ ## Contributing
215
+
216
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Cat1237/hashtable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[Cat1237]/hashtable/blob/master/CODE_OF_CONDUCT.md).
217
+
218
+
219
+ ## License
220
+
221
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
222
+
223
+ ## Code of Conduct
224
+
225
+ Everyone interacting in the Hashtable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Cat1237/hashtable/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ # module HashTable
4
+ module HashTable
5
+ # HashTable
6
+ # @abstract
7
+ class HashTable
8
+ # @return [Integer] nums of HashTable entries
9
+ attr_reader :num_entries
10
+
11
+ def initialize(capacity = 8, expand: false)
12
+ capacity = capacity < 8 ? 8 : 2**(capacity - 1).bit_length
13
+ @buckets = Array.new(capacity)
14
+ @present = SparseBitArray.new
15
+ @deleted = SparseBitArray.new
16
+ @expand = expand
17
+ @num_entries = 0
18
+ end
19
+
20
+ def size
21
+ @present.count
22
+ end
23
+
24
+ def empty?
25
+ size.zero?
26
+ end
27
+
28
+ def capacity
29
+ @buckets.length
30
+ end
31
+
32
+ # @return [Integer] Find the entry whose key has the specified hash value,
33
+ # using the specified traits defining hash function and equality.
34
+ # @param [object] key
35
+ # @param [object] traits
36
+ def find(key, traits)
37
+ raise ArgumentError, 'traits must respond to hash_lookup_key method' unless traits.respond_to?(:hash_lookup_key)
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
44
+ i = h
45
+ fisrt_unsed = nil
46
+ loop do
47
+ if present?(i)
48
+ return i if !@buckets[i].nil? && traits.storage_key_to_lookup_key(@buckets[i].first) == key
49
+ else
50
+ fisrt_unsed = i if fisrt_unsed.nil?
51
+ break unless deleted?(i)
52
+ end
53
+ i = (i + 1) % capacity
54
+ break if i == h
55
+ end
56
+ raise ArgumentError if fisrt_unsed.nil?
57
+
58
+ fisrt_unsed
59
+ end
60
+
61
+ # @return [Integer] internal key
62
+ # Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
63
+ # @param [object] key
64
+ # @param [object] value
65
+ # @param [object] traits
66
+ def set(key, value, traits)
67
+ set_as_interal(key, traits, value)
68
+ end
69
+
70
+ def get(key, traits)
71
+ i = find(key, traits)
72
+ bucket = @buckets[i]
73
+ raise ArgumentError if bucket.nil? || bucket.empty?
74
+
75
+ bucket.last
76
+ end
77
+
78
+ # @return [Integer] internal key
79
+ # Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
80
+ # @param [object] key
81
+ # @param [object] traits
82
+ def add(key, traits)
83
+ set_as_interal(key, traits)
84
+ end
85
+
86
+ # @return [Array<Integer>] internal key
87
+ # Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
88
+ # @param [object] key
89
+ # @param [object] traits
90
+ def adds(keys, traits)
91
+ (keys || []).map { |key| set_as_interal(key, traits) }
92
+ end
93
+
94
+ protected
95
+
96
+ attr_reader :present, :deleted, :buckets
97
+
98
+ def present?(key)
99
+ @present.test?(key)
100
+ end
101
+
102
+ def deleted?(key)
103
+ @present.test?(key)
104
+ end
105
+
106
+ # @return [Integer] internal key
107
+ # Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
108
+ # @param [object] key
109
+ # @param [object] traits
110
+ # @param [object] value
111
+ # @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
+
122
+ index = find(key, traits)
123
+ bucket = @buckets[index] ||= []
124
+ if bucket.empty?
125
+ raise ArgumentError if present?(index)
126
+
127
+ bucket[0] = internal_key.nil? ? traits.lookup_key_to_storage_key(key) : internal_key
128
+ bucket[1] = value unless value.nil?
129
+ @present.set(index)
130
+ @deleted.reset(index)
131
+ grow(traits)
132
+ else
133
+ raise ArgumentError unless present?(index)
134
+ raise ArgumentError unless traits.storage_key_to_lookup_key(@buckets[index].first) == key
135
+
136
+ bucket[1] = value unless value.nil?
137
+ end
138
+ bucket[0]
139
+ end
140
+
141
+ private
142
+
143
+ def grow(traits)
144
+ unless traits.respond_to?(:storage_key_to_lookup_key)
145
+ raise ArgumentError,
146
+ 'traits must respond to storage_key_to_lookup_key method'
147
+ end
148
+ @num_entries += 1
149
+ n = 2**(@num_entries - 1).bit_length
150
+ m = n < 8 ? 8 : n
151
+ max_load = m * 2 / 3 + 1
152
+ entries = @expand ? num_entries : size
153
+ return if entries < max_load
154
+
155
+ @num_entries = m + 1 if @expand
156
+ new_capacity = m * 2
157
+ return if new_capacity <= capacity
158
+
159
+ new_map = HashTable.new(new_capacity, expand: @expand)
160
+ @present.each do |i|
161
+ lookup_key = traits.storage_key_to_lookup_key(@buckets[i].first)
162
+ # Private methods cannot be called with an explicit receiver and protected ones can.
163
+ new_map.set_as_interal(lookup_key, traits, @buckets[i][1], @buckets[i].first)
164
+ end
165
+ @buckets = new_map.buckets
166
+ @present = new_map.present
167
+ @deleted = new_map.deleted
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ # module HashTable
4
+ module HashTable
5
+ BIT_WORD = 64
6
+ ELEMENT_SIZE = BIT_WORD * 2
7
+ UINT_64_MAX = 2**BIT_WORD - 1
8
+ BITWORD_SIZE = BIT_WORD
9
+ BITWORDS_PER_ELEMENT = (ELEMENT_SIZE + BITWORD_SIZE - 1) / BITWORD_SIZE
10
+ BITS_PER_ELEMENT = ELEMENT_SIZE
11
+ private_constant :BIT_WORD, :ELEMENT_SIZE, :UINT_64_MAX, :BITWORD_SIZE
12
+ private_constant :BITWORDS_PER_ELEMENT, :BITS_PER_ELEMENT, :BITS_PER_ELEMENT
13
+ # SparseBitArrayElement
14
+ # @abstract
15
+ class SparseBitArrayElement
16
+ # @return [Integer] Index of Element in terms of where first bit starts.
17
+ attr_reader :index
18
+
19
+ def initialize(index = 0)
20
+ @index = index
21
+ # Index of Element in terms of where first bit starts.
22
+ @bits = Array.new(BITWORDS_PER_ELEMENT, 0)
23
+ end
24
+
25
+ # @return [Integer] the bits that make up word index in our element
26
+ # @param [Integer] index
27
+ def word(index)
28
+ raise ArgumentError, 'index error' unless index < BITWORDS_PER_ELEMENT
29
+
30
+ @bits[index]
31
+ end
32
+
33
+ def empty?
34
+ (0...BITWORDS_PER_ELEMENT).each do |i|
35
+ return false unless @bits[i].zero?
36
+ end
37
+ true
38
+ end
39
+
40
+ def set(index)
41
+ @bits[index / BITWORD_SIZE] |= 1 << (index % BITWORD_SIZE)
42
+ end
43
+
44
+ def test?(index)
45
+ @bits[index / BITWORD_SIZE] & (1 << (index % BITWORD_SIZE)) != 0
46
+ end
47
+
48
+ def test_and_set?(index)
49
+ unless test(index)
50
+ set(Idx)
51
+ return true
52
+ end
53
+ false
54
+ end
55
+
56
+ def reset(index)
57
+ bits[index / BITWORD_SIZE] &= ~(1 << (index % BITWORD_SIZE))
58
+ end
59
+
60
+ # v = @bits[i]
61
+ # v -= ((v >> 1) & 0x5555555555555555)
62
+ # v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
63
+ # v = (v + (v >> 4) & 0x0F0F0F0F0F0F0F0F)
64
+ # v = (v * 0x0101010101010101) & UINT_64_MAX
65
+ # v >>= 56
66
+ def count
67
+ (0...BITWORDS_PER_ELEMENT).inject(0) do |nums, i|
68
+ v = @bits[i].digits(2).count(1)
69
+ nums + v
70
+ end
71
+ end
72
+
73
+ # @return [Integer] the index of the first set bit
74
+ def first
75
+ (0...BITWORDS_PER_ELEMENT).each do |i|
76
+ v = @bits[i]
77
+ next if v.zero?
78
+
79
+ count = v.digits(2).index(1)
80
+ return i * BITWORD_SIZE + count
81
+ end
82
+ end
83
+
84
+ # @return [Integer] the index of the last set bit
85
+ def last
86
+ (0...BITWORDS_PER_ELEMENT).each do |i|
87
+ index = BITWORDS_PER_ELEMENT - i - 1
88
+ v = @bits[index]
89
+ next unless v != 0
90
+
91
+ count = BIT_WORD - v.bit_length
92
+ return index * BITWORD_SIZE + BITWORD_SIZE - count - 1
93
+ end
94
+ end
95
+
96
+ # @return [Integer] the index of the next set bit starting from the "index" bit.
97
+ # Returns -1 if the next set bit is not found.
98
+ def next(index)
99
+ return -1 if index >= BITS_PER_ELEMENT
100
+
101
+ word_pos = index / BITWORD_SIZE
102
+ bit_pos = index % BITWORD_SIZE
103
+ copy = @bits[word_pos]
104
+ raise ArgumentError, 'Word Position outside of element' unless word_pos <= BITS_PER_ELEMENT
105
+
106
+ copy &= ~0 << bit_pos
107
+ return word_pos * BITWORD_SIZE + copy.digits(2).index(1) unless copy.zero?
108
+
109
+ word_pos += 1
110
+ (word_pos...BITWORDS_PER_ELEMENT).each do |i|
111
+ return i * BITWORD_SIZE + @bits[i].digits(2).index(1) unless @bits[i].zero?
112
+ end
113
+ -1
114
+ end
115
+
116
+ include Enumerable
117
+
118
+ def each
119
+ return if empty?
120
+
121
+ last_i = last
122
+ first_i = first
123
+ bits = 0
124
+ bit_number = 0
125
+ loop do
126
+ while bits.nonzero? && (bits & 1).zero?
127
+ bits >>= 1
128
+ bit_number += 1
129
+ end
130
+ # See if we ran out of Bits in this word.
131
+ if bits.zero?
132
+ next_set_bit_number = self.next(bit_number % ELEMENT_SIZE)
133
+ if next_set_bit_number == -1 || (bit_number % ELEMENT_SIZE).zero?
134
+ next_set_bit_number = first_i
135
+ bit_number = index * ELEMENT_SIZE
136
+ bit_number += next_set_bit_number
137
+ word_number = bit_number % ELEMENT_SIZE / BITWORD_SIZE
138
+ bits = word(word_number)
139
+ bits >>= next_set_bit_number % BITWORD_SIZE
140
+ else
141
+ # Set up for next non-zero word in bitmap
142
+ word_number = next_set_bit_number % ELEMENT_SIZE / BITWORD_SIZE
143
+ bits = word(word_number)
144
+ bits >>= next_set_bit_number % BITWORD_SIZE
145
+ bit_number = index * ELEMENT_SIZE
146
+ bit_number += next_set_bit_number
147
+ end
148
+ end
149
+ yield bit_number
150
+ break if bit_number % ELEMENT_SIZE == last_i
151
+
152
+ bit_number += 1
153
+ bits >>= 1
154
+ end
155
+ end
156
+ end
157
+
158
+ # SparseBitArray is an implementation of a bitmap that is sparse by only storing the elements that have non-zero bits set.
159
+ # @abstract
160
+ class SparseBitArray
161
+ # @return [Array<SparseBitArrayElement>] The list of Elements
162
+ attr_reader :elements
163
+ # @return [Integer] Pointer to our current Element.
164
+ # This has no visible effect on the external state of a SparseBitArray
165
+ # It's just used to improve performance in the common case of testing/modifying bits with similar indices.
166
+ attr_reader :current_index
167
+
168
+ def initialize
169
+ @elements = []
170
+ @current_index = -1
171
+ end
172
+
173
+ # @return [Boolean] Test a bit in the bitmap
174
+ # @param [Integer] bit
175
+ def test?(index)
176
+ return false if elements.empty?
177
+
178
+ e_index = index / ELEMENT_SIZE
179
+ element_i = lower_bound(e_index)
180
+ last = elements.length
181
+ return false if element_i == last || elements[element_i].index != e_index
182
+
183
+ elements[element_i].test?(index % ELEMENT_SIZE)
184
+ end
185
+
186
+ # @return [Void] Reset a bit in the bitmap
187
+ # @param [Integer] bit
188
+ def reset(index)
189
+ return if @elements.empty?
190
+
191
+ e_index = index / ELEMENT_SIZE
192
+ element_i = lower_bound(e_index)
193
+ element = elements[element_i]
194
+ return if element_i == elements.length || element.index != e_index
195
+
196
+ element.reset(index % ELEMENT_SIZE)
197
+ return unless element.empty?
198
+
199
+ @elements.delete_at(element_i)
200
+ end
201
+
202
+ # @return [Void] Set a bit in the bitmap
203
+ # @param [Integer] bit
204
+ def set(index)
205
+ e_index = index / ELEMENT_SIZE
206
+ element_i = lower_bound(e_index)
207
+ new_e = elements[element_i]
208
+ unless new_e.nil?
209
+ element_i += 1 if new_e.index < e_index
210
+ new_e = nil if new_e.index != e_index
211
+ end
212
+ @current_index = element_i
213
+ @elements.insert(@current_index, SparseBitArrayElement.new(e_index)) if new_e.nil?
214
+ @elements[@current_index].set(index % ELEMENT_SIZE)
215
+ end
216
+
217
+ # @return [Void] Test, Set a bit in the bitmap
218
+ # @param [Integer] bit
219
+ def test_and_set?(index)
220
+ unless test?(index)
221
+ set(Idx)
222
+ return true
223
+ end
224
+ false
225
+ end
226
+
227
+ def count
228
+ @elements.inject(0) { |c, e| c + e.count }
229
+ end
230
+
231
+ def clear
232
+ @elements.clear
233
+ end
234
+
235
+ # @return [Integer] the first set bit in the bitmap.
236
+ # Return -1 if no bits are set.
237
+ def first
238
+ return -1 if elements.empty?
239
+
240
+ first = elements.first
241
+ first.index * ELEMENT_SIZE + first.first
242
+ end
243
+
244
+ # @return [Integer] the last set bit in the bitmap.
245
+ # Return -1 if no bits are set.
246
+ def last
247
+ return -1 if elements.empty?
248
+
249
+ last = elements.last
250
+ last.index * ELEMENT_SIZE + last.last
251
+ end
252
+
253
+ def empty?
254
+ elements.empty?
255
+ end
256
+
257
+ include Enumerable
258
+
259
+ def each(&block)
260
+ @elements.each { |element| element.each(&block) }
261
+ end
262
+
263
+ private
264
+
265
+ # @return [Integer] do linear searching from the current position.
266
+ def lower_bound(index)
267
+ return 0 if elements.empty?
268
+
269
+ @current_index -= 1 if @current_index == elements.length
270
+ element_i = @current_index
271
+ element = @elements[element_i]
272
+ return element_i if element.index == index
273
+
274
+ @current_index = if element.index > index
275
+ @elements[0..element_i].rindex { |e| e.index <= index } || 0
276
+ else
277
+ @elements[element_i..-1].select { |e| e.index < index }.length + element_i
278
+ end
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,3 @@
1
+ module Hashtable
2
+ VERSION = '0.1.1'
3
+ end
data/lib/hashtable.rb ADDED
@@ -0,0 +1,5 @@
1
+ module HashTable
2
+ require_relative 'hashtable/version'
3
+ require_relative 'hashtable/sparse_bit_array'
4
+ require_relative 'hashtable/hash_table'
5
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hashtable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Cat1237
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: This does some funky memory allocation and hashing things to make it
56
+ extremely efficient, storing the key/value with `SparseBitArray`
57
+ email:
58
+ - wangson1237@outlook.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE
64
+ - README.md
65
+ - lib/hashtable.rb
66
+ - lib/hashtable/hash_table.rb
67
+ - lib/hashtable/sparse_bit_array.rb
68
+ - lib/hashtable/version.rb
69
+ homepage: https://github.com/Cat1237/hashtable.git
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '2.6'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.1.6
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: This provides a hash table data structure that is specialized for handling
92
+ key/value pairs.
93
+ test_files: []