hashtable 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5852fb24336d190f81a89d83eb6b23184f37972507641ac2e0b78c323a3ef65
4
- data.tar.gz: a809dbacc1536fd55d18e30d677aba9fdbe331cdda5f2612942a394fa7bad5f7
3
+ metadata.gz: 9a2d080d4eda5fb41e97c4661131fe7b3124530d4cb3604e907ca1294b38fe20
4
+ data.tar.gz: 456c01089c6b9764016386137ef4be6ac247fbef83921861d803659c798d9dec
5
5
  SHA512:
6
- metadata.gz: 044d20bcefe08d1c84d7a7fec897fc4d2f892b5a29c28e8f373ddb804039fc0679f9bb1b52992415e3c9cf8f0d62adf39ba42bd4deee7181422062b97916e01f
7
- data.tar.gz: 47d1688da243dd2b5377f880bee64e7b91709b7b1efd0ff252d8a85849f5b5bfe155f68ffe780e8a87689e96e379849001c4757727a977d4b809faecb6cdd700
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
- 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)
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 = StringTraits.new
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
- # 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
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
- 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
-
97
+ table = HashTable::HashTable.new
98
+ traits = HashTable::StringIdentityHashTraits.new
156
99
  (0..31).each do |i|
157
- table.add("ViewController#{i}.h", traits)
100
+ table.add("ViewController#{i}.h", traits)
158
101
  end
159
- # 32
160
- p table.size
161
- # 64
162
- p table.capacity
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
- 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
115
+ table = HashTable::HashTable.new(1364, expand: true)
116
+ traits = HashTable::StringHashTraits.new
202
117
  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)
118
+ table.set("ViewController#{i}.h",
119
+ ["/Users/ws/Desktop/llvm/TestAndTestApp/TestAndTestApp/Group/h2/#{i}", "ViewController#{i}.h"], traits)
205
120
  end
206
- # 2728
207
- p table.size
208
- # 8192
209
- p table.capacity
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
@@ -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
- # @return [Integer] nums of HashTable entries
9
- attr_reader :num_entries
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
- def initialize(capacity = 8, expand: false)
12
- capacity = capacity < 8 ? 8 : 2**(capacity - 1).bit_length
13
- @buckets = Array.new(capacity)
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
- @num_entries = 0
72
+ @size = 0
18
73
  end
19
74
 
20
- def size
21
- @present.count
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
- @buckets.length
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
- 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
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
- 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
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
- raise ArgumentError if fisrt_unsed.nil?
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
- bucket = @buckets[i]
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, :buckets
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
- bucket = @buckets[index] ||= []
124
- if bucket.empty?
169
+ bucket_k = @buckets_k[index]
170
+ if bucket_k.nil?
125
171
  raise ArgumentError if present?(index)
126
172
 
127
- bucket[0] = internal_key.nil? ? traits.lookup_key_to_storage_key(key) : internal_key
128
- bucket[1] = value unless value.nil?
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
- bucket[1] = value unless value.nil?
182
+ @buckets_v[index] = traits.lookup_key_to_storage_value(bucket_k, value)
137
183
  end
138
- return index if traits.respond_to?(:lookup_key_and_value)
139
-
140
- bucket[0]
184
+ bucket_k
141
185
  end
142
186
 
143
187
  private
144
188
 
145
- def grow(traits)
146
- unless traits.respond_to?(:storage_key_to_lookup_key)
147
- raise ArgumentError,
148
- 'traits must respond to storage_key_to_lookup_key method'
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
- @num_entries += 1
151
- n = 2**(@num_entries - 1).bit_length
152
- m = n < 8 ? 8 : n
153
- max_load = m * 2 / 3 + 1
154
- entries = @expand ? num_entries : size
155
- return if entries < max_load
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
- lookup_key = traits.storage_key_to_lookup_key(@buckets[i].first)
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, @buckets[i][1], @buckets[i].first)
217
+ new_map.set_as_interal(lookup_key, traits, @buckets_v[i], key)
166
218
  end
167
- @buckets = new_map.buckets
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
- return true
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
- (0...BITWORDS_PER_ELEMENT).inject(0) do |nums, i|
68
- v = @bits[i].digits(2).count(1)
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
- next if v.zero?
78
-
79
- count = v.digits(2).index(1)
80
- return i * BITWORD_SIZE + count
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
- next unless v != 0
90
-
91
- count = BIT_WORD - v.bit_length
92
- return index * BITWORD_SIZE + BITWORD_SIZE - count - 1
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
- last = elements.length
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
- element_i += 1 if new_e.index < e_index
210
- new_e = nil if new_e.index != e_index
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
- @elements.insert(@current_index, SparseBitArrayElement.new(e_index)) if new_e.nil?
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 = if element.index > 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
- else
277
- @elements[element_i..].select { |e| e.index < index }.length + 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
@@ -1,84 +1,65 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: false
2
2
 
3
3
  # module HashTable
4
4
  module HashTable
5
- class Traits
6
- def hash_lookup_key(key)
7
- key
8
- end
9
-
10
- def lookup_key_to_storage_key(key)
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
- class StringTraits
20
- attr_reader :string_table, :string_index
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
- @string_index = 1
20
+ @buckets = {}
21
+ @indexs = {}
25
22
  end
26
23
 
27
24
  def hash_lookup_key(key)
28
- result = 0
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
- @string_table += "#{key}\0"
35
- old_si = @string_index
36
- @string_index += key.length + 1
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
- @string_table[offset..][/[^\0]+/]
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
- def initialize
49
- @string_table = "\0"
50
- @string_index = 1
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
- def hash_lookup_key(key)
56
- return @hash_h[key] unless @hash_h[key].nil?
47
+ # StringHashTraits
48
+ class StringHashTraits < StringIdentityHashTraits
49
+ attr_reader :string_table, :buckets
57
50
 
58
- result = 0
59
- key.each_byte { |byte| result += byte * 13 }
60
- @hash_h[key] = result
61
- result
51
+ def initialize(&block)
52
+ super
53
+ @block = block
62
54
  end
63
55
 
64
- def lookup_key_to_storage_key(key)
65
- return @buckets[key] unless @buckets[key].nil?
56
+ def lookup_key_to_storage_value(key, values)
57
+ return if values.nil? || values.empty?
66
58
 
67
- @string_table += "#{key}\0"
68
- old_si = @string_index
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
- def lookup_key_and_value(key, value)
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
@@ -1,3 +1,3 @@
1
1
  module Hashtable
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
data/lib/hashtable.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module HashTable
2
2
  require_relative 'hashtable/version'
3
3
  require_relative 'hashtable/sparse_bit_array'
4
- require_relative 'hashtable/hash_table'
5
4
  require_relative 'hashtable/traits'
6
- end
5
+ autoload :HashTable, 'hashtable/hash_table'
6
+ end
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.3
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-19 00:00:00.000000000 Z
11
+ date: 2022-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler