1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
|
module Immutable
# @private
class Trie
def self.[](pairs)
result = new(0)
pairs.each { |key, val| result.put!(key, val) }
result
end
# Returns the number of key-value pairs in the trie.
attr_reader :size
def initialize(bitshift, size = 0, entries = [], children = [])
@bitshift = bitshift
@entries = entries
@children = children
@size = size
end
# Returns <tt>true</tt> if the trie contains no key-value pairs.
def empty?
@size == 0
end
# Returns <tt>true</tt> if the given key is present in the trie.
def key?(key)
!!get(key)
end
# Calls <tt>block</tt> once for each entry in the trie, passing the key-value pair as parameters.
def each(&block)
@entries.each { |entry| yield entry if entry }
@children.each do |child|
child.each(&block) if child
end
nil
end
def reverse_each(&block)
@children.reverse_each do |child|
child.reverse_each(&block) if child
end
@entries.reverse_each { |entry| yield(entry) if entry }
nil
end
def reduce(memo)
each { |entry| memo = yield(memo, entry) }
memo
end
def select
keys_to_delete = []
each { |entry| keys_to_delete << entry[0] unless yield(entry) }
bulk_delete(keys_to_delete)
end
# @return [Trie] A copy of `self` with the given value associated with the
# key (or `self` if no modification was needed because an identical
# key-value pair was already stored
def put(key, value)
index = index_for(key)
entry = @entries[index]
if !entry
entries = @entries.dup
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
entries[index] = [key, value].freeze
Trie.new(@bitshift, @size + 1, entries, @children)
elsif entry[0].eql?(key)
if entry[1].equal?(value)
self
else
entries = @entries.dup
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
entries[index] = [key, value].freeze
Trie.new(@bitshift, @size, entries, @children)
end
else
child = @children[index]
if child
new_child = child.put(key, value)
if new_child.equal?(child)
self
else
children = @children.dup
children[index] = new_child
new_self_size = @size + (new_child.size - child.size)
Trie.new(@bitshift, new_self_size, @entries, children)
end
else
children = @children.dup
children[index] = Trie.new(@bitshift + 5).put!(key, value)
Trie.new(@bitshift, @size + 1, @entries, children)
end
end
end
# Put multiple elements into a Trie. This is more efficient than several
# calls to `#put`.
#
# @param key_value_pairs Enumerable of pairs (`[key, value]`)
# @return [Trie] A copy of `self` after associated the given keys and
# values (or `self` if no modifications where needed).
def bulk_put(key_value_pairs)
new_entries = nil
new_children = nil
new_size = @size
key_value_pairs.each do |key, value|
index = index_for(key)
entry = (new_entries || @entries)[index]
if !entry
new_entries ||= @entries.dup
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
new_entries[index] = [key, value].freeze
new_size += 1
elsif entry[0].eql?(key)
if !entry[1].equal?(value)
new_entries ||= @entries.dup
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
new_entries[index] = [key, value].freeze
end
else
child = (new_children || @children)[index]
if child
new_child = child.put(key, value)
if !new_child.equal?(child)
new_children ||= @children.dup
new_children[index] = new_child
new_size += new_child.size - child.size
end
else
new_children ||= @children.dup
new_children[index] = Trie.new(@bitshift + 5).put!(key, value)
new_size += 1
end
end
end
if new_entries || new_children
Trie.new(@bitshift, new_size, new_entries || @entries, new_children || @children)
else
self
end
end
# Returns <tt>self</tt> after overwriting the element associated with the specified key.
def put!(key, value)
index = index_for(key)
entry = @entries[index]
if !entry
@size += 1
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
@entries[index] = [key, value].freeze
elsif entry[0].eql?(key)
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
@entries[index] = [key, value].freeze
else
child = @children[index]
if child
old_child_size = child.size
@children[index] = child.put!(key, value)
@size += child.size - old_child_size
else
@children[index] = Trie.new(@bitshift + 5).put!(key, value)
@size += 1
end
end
self
end
# Retrieves the entry corresponding to the given key. If not found, returns <tt>nil</tt>.
def get(key)
index = index_for(key)
entry = @entries[index]
if entry && entry[0].eql?(key)
entry
else
child = @children[index]
child.get(key) if child
end
end
# Returns a copy of <tt>self</tt> with the given key (and associated value) deleted. If not found, returns <tt>self</tt>.
def delete(key)
find_and_delete(key) || Trie.new(@bitshift)
end
# Delete multiple elements from a Trie. This is more efficient than
# several calls to `#delete`.
#
# @param keys [Enumerable] The keys to delete
# @return [Trie]
def bulk_delete(keys)
new_entries = nil
new_children = nil
new_size = @size
keys.each do |key|
index = index_for(key)
entry = (new_entries || @entries)[index]
if !entry
next
elsif entry[0].eql?(key)
new_entries ||= @entries.dup
child = (new_children || @children)[index]
if child
# Bring up the first entry from the child into entries
new_children ||= @children.dup
new_children[index] = child.delete_at do |entry|
new_entries[index] = entry
end
else
new_entries[index] = nil
end
new_size -= 1
else
child = (new_children || @children)[index]
if child
copy = child.find_and_delete(key)
unless copy.equal?(child)
new_children ||= @children.dup
new_children[index] = copy
new_size -= (child.size - copy_size(copy))
end
end
end
end
if new_entries || new_children
Trie.new(@bitshift, new_size, new_entries || @entries, new_children || @children)
else
self
end
end
def include?(key, value)
entry = get(key)
entry && value.eql?(entry[1])
end
def at(index)
@entries.each do |entry|
if entry
return entry if index == 0
index -= 1
end
end
@children.each do |child|
if child
if child.size >= index+1
return child.at(index)
else
index -= child.size
end
end
end
nil
end
# Returns <tt>true</tt> if . <tt>eql?</tt> is synonymous with <tt>==</tt>
def eql?(other)
return true if equal?(other)
return false unless instance_of?(other.class) && size == other.size
each do |entry|
return false unless other.include?(entry[0], entry[1])
end
true
end
alias == eql?
protected
# Returns a replacement instance after removing the specified key.
# If not found, returns <tt>self</tt>.
# If empty, returns <tt>nil</tt>.
def find_and_delete(key)
index = index_for(key)
entry = @entries[index]
if entry && entry[0].eql?(key)
return delete_at(index)
else
child = @children[index]
if child
copy = child.find_and_delete(key)
unless copy.equal?(child)
children = @children.dup
children[index] = copy
new_size = @size - (child.size - copy_size(copy))
return Trie.new(@bitshift, new_size, @entries, children)
end
end
end
self
end
# Returns a replacement instance after removing the specified entry. If empty, returns <tt>nil</tt>
def delete_at(index = @entries.index { |e| e })
yield(@entries[index]) if block_given?
if size > 1
entries = @entries.dup
child = @children[index]
if child
children = @children.dup
children[index] = child.delete_at do |entry|
entries[index] = entry
end
else
entries[index] = nil
end
Trie.new(@bitshift, @size - 1, entries, children || @children)
end
end
private
def index_for(key)
(key.hash.abs >> @bitshift) & 31
end
def copy_size(copy)
copy ? copy.size : 0
end
end
# @private
EmptyTrie = Trie.new(0).freeze
end
|