hashtable 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +225 -0
- data/lib/hashtable/hash_table.rb +170 -0
- data/lib/hashtable/sparse_bit_array.rb +281 -0
- data/lib/hashtable/version.rb +3 -0
- data/lib/hashtable.rb +5 -0
- metadata +93 -0
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
|
+
[](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
|
data/lib/hashtable.rb
ADDED
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: []
|