sorted_containers 0.0.1 → 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 +4 -4
- data/.rubocop.yml +0 -3
- data/CHANGELOG.md +5 -1
- data/README.md +133 -10
- data/lib/sorted_containers/sorted_array.rb +854 -0
- data/lib/sorted_containers/sorted_hash.rb +152 -0
- data/lib/sorted_containers/sorted_set.rb +82 -9
- data/lib/sorted_containers/version.rb +1 -1
- data/lib/sorted_containers.rb +2 -5
- metadata +9 -7
- data/lib/sorted_containers/sorted_list.rb +0 -174
@@ -0,0 +1,854 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The SortedContainers module provides data structures for sorted collections.
|
4
|
+
# rubocop:disable Metrics/ClassLength
|
5
|
+
module SortedContainers
|
6
|
+
# The SortedArray class is a sorted array implementation.
|
7
|
+
class SortedArray
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# The default load factor for the array.
|
11
|
+
# Sublists are split when they exceed 2 * load_factor
|
12
|
+
DEFAULT_LOAD_FACTOR = 1000
|
13
|
+
|
14
|
+
attr_reader :size
|
15
|
+
alias length size
|
16
|
+
|
17
|
+
# Initializes a new SortedArray object.
|
18
|
+
#
|
19
|
+
# @param iterable [Enumerable] An optional iterable object to initialize the array with.
|
20
|
+
# @param load_factor [Integer] The load factor for the array.
|
21
|
+
def initialize(iterable = [], load_factor: DEFAULT_LOAD_FACTOR)
|
22
|
+
@lists = []
|
23
|
+
@maxes = []
|
24
|
+
@index = []
|
25
|
+
@offset = 0
|
26
|
+
@load_factor = load_factor
|
27
|
+
@size = 0
|
28
|
+
update(iterable)
|
29
|
+
end
|
30
|
+
|
31
|
+
# rubocop:disable Metrics/MethodLength
|
32
|
+
|
33
|
+
# Adds a value to the sorted array.
|
34
|
+
#
|
35
|
+
# @param value [Object] The value to add.
|
36
|
+
def add(value)
|
37
|
+
if @maxes.empty?
|
38
|
+
@lists.append([value])
|
39
|
+
@maxes.append(value)
|
40
|
+
else
|
41
|
+
pos = internal_bisect_right(@maxes, value)
|
42
|
+
|
43
|
+
if pos == @maxes.size
|
44
|
+
pos -= 1
|
45
|
+
@lists[pos].push(value)
|
46
|
+
@maxes[pos] = value
|
47
|
+
else
|
48
|
+
sub_pos = internal_bisect_right(@lists[pos], value)
|
49
|
+
@lists[pos].insert(sub_pos, value)
|
50
|
+
end
|
51
|
+
expand(pos)
|
52
|
+
end
|
53
|
+
@size += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:enable Metrics/MethodLength
|
57
|
+
|
58
|
+
# Alias for add
|
59
|
+
#
|
60
|
+
# @param value [Object] The value to add.
|
61
|
+
def <<(value)
|
62
|
+
add(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a string representation of the sorted array.
|
66
|
+
#
|
67
|
+
# @return [String] A string representation of the sorted array.
|
68
|
+
def to_s
|
69
|
+
"SortedArray(#{to_a})"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Checks if Array is empty
|
73
|
+
#
|
74
|
+
# @return [Boolean]
|
75
|
+
def empty?
|
76
|
+
@size.zero?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns an index to insert `value` in the sorted list.
|
80
|
+
#
|
81
|
+
# If the `value` is already present, the insertion point will be before
|
82
|
+
# (to the left of) any existing values.
|
83
|
+
#
|
84
|
+
# Runtime complexity: `O(log(n))` -- approximate.
|
85
|
+
#
|
86
|
+
# @param value [Object] The value to insert.
|
87
|
+
# @return [Integer] The index to insert the value.
|
88
|
+
def bisect_left(value)
|
89
|
+
return 0 if @maxes.empty?
|
90
|
+
|
91
|
+
pos = internal_bisect_left(@maxes, value)
|
92
|
+
|
93
|
+
return @size if pos == @maxes.size
|
94
|
+
|
95
|
+
idx = internal_bisect_left(@lists[pos], value)
|
96
|
+
loc(pos, idx)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns an index to insert `value` in the sorted list.
|
100
|
+
#
|
101
|
+
# If the `value` is already present, the insertion point will be after
|
102
|
+
# (to the right of) any existing values.
|
103
|
+
#
|
104
|
+
# Runtime complexity: `O(log(n))` -- approximate.
|
105
|
+
#
|
106
|
+
# @param value [Object] The value to insert.
|
107
|
+
# @return [Integer] The index to insert the value.
|
108
|
+
def bisect_right(value)
|
109
|
+
return 0 if @maxes.empty?
|
110
|
+
|
111
|
+
pos = internal_bisect_right(@maxes, value)
|
112
|
+
|
113
|
+
return @size if pos == @maxes.size
|
114
|
+
|
115
|
+
idx = internal_bisect_right(@lists[pos], value)
|
116
|
+
loc(pos, idx)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Deletes a value from the sorted array.
|
120
|
+
#
|
121
|
+
# @param value [Object] The value to delete.
|
122
|
+
def delete(value)
|
123
|
+
return if @maxes.empty?
|
124
|
+
|
125
|
+
pos = internal_bisect_left(@maxes, value)
|
126
|
+
|
127
|
+
return if pos == @maxes.size
|
128
|
+
|
129
|
+
idx = internal_bisect_left(@lists[pos], value)
|
130
|
+
|
131
|
+
internal_delete(pos, idx) if @lists[pos][idx] == value
|
132
|
+
end
|
133
|
+
|
134
|
+
# Tries to match the behavior of Array#[]
|
135
|
+
# alias for slice
|
136
|
+
#
|
137
|
+
# @param args [Integer, Range, Enumerator::ArithmeticSequence] The index or range of values to retrieve.
|
138
|
+
# @return [Object, Array] The value or values at the specified index or range.
|
139
|
+
def [](*args)
|
140
|
+
slice(*args)
|
141
|
+
end
|
142
|
+
|
143
|
+
# rubocop:disable Metrics/MethodLength
|
144
|
+
|
145
|
+
# Tries to match the behavior of Array#slice
|
146
|
+
#
|
147
|
+
# @param args [Integer, Range, Enumerator::ArithmeticSequence] The index or range of values to retrieve.
|
148
|
+
# @return [Object, Array] The value or values at the specified index or range.
|
149
|
+
def slice(*args)
|
150
|
+
case args.size
|
151
|
+
when 1
|
152
|
+
arg = args[0]
|
153
|
+
case arg
|
154
|
+
when Integer
|
155
|
+
get_value_at_index(arg)
|
156
|
+
when Range
|
157
|
+
get_values_from_range(arg)
|
158
|
+
when Enumerator::ArithmeticSequence
|
159
|
+
get_values_from_arithmetic_sequence(arg)
|
160
|
+
else
|
161
|
+
raise TypeError, "no implicit conversion of #{arg.class} into Integer"
|
162
|
+
end
|
163
|
+
when 2
|
164
|
+
start, length = args
|
165
|
+
get_values_from_start_and_length(start, length)
|
166
|
+
else
|
167
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1..2)"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
# rubocop:enable Metrics/MethodLength
|
171
|
+
|
172
|
+
# rubocop:disable Metrics/MethodLength
|
173
|
+
# rubocop:disable Metrics/AbcSize
|
174
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
175
|
+
|
176
|
+
# Tries to match the behavior of Array#slice!
|
177
|
+
#
|
178
|
+
# @param args [Integer, Range, Enumerator::ArithmeticSequence] The index or range of values to retrieve.
|
179
|
+
# @return [Object, Array] The value or values at the specified index or range.
|
180
|
+
def slice!(*args)
|
181
|
+
case args.size
|
182
|
+
when 1
|
183
|
+
arg = args[0]
|
184
|
+
case arg
|
185
|
+
when Integer
|
186
|
+
value = get_value_at_index(arg)
|
187
|
+
delete_at(arg)
|
188
|
+
value
|
189
|
+
when Range
|
190
|
+
values = get_values_from_range(arg)
|
191
|
+
values.each { |val| delete(val) }
|
192
|
+
values
|
193
|
+
when Enumerator::ArithmeticSequence
|
194
|
+
values = get_values_from_arithmetic_sequence(arg)
|
195
|
+
values.each { |val| delete(val) }
|
196
|
+
values
|
197
|
+
else
|
198
|
+
raise TypeError, "no implicit conversion of #{arg.class} into Integer"
|
199
|
+
end
|
200
|
+
when 2
|
201
|
+
start, length = args
|
202
|
+
values = get_values_from_start_and_length(start, length)
|
203
|
+
values.each { |val| delete(val) }
|
204
|
+
values
|
205
|
+
else
|
206
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1..2)"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
# rubocop:enable Metrics/MethodLength
|
210
|
+
# rubocop:enable Metrics/AbcSize
|
211
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
212
|
+
|
213
|
+
# Retrieves the last value in the sorted array.
|
214
|
+
#
|
215
|
+
# @return [Object] The last value in the array.
|
216
|
+
def last
|
217
|
+
return nil if @size.zero?
|
218
|
+
|
219
|
+
@lists.last.last
|
220
|
+
end
|
221
|
+
|
222
|
+
# Retrieves the first value in the sorted array.
|
223
|
+
#
|
224
|
+
# @return [Object] The first value in the array.
|
225
|
+
def first
|
226
|
+
return nil if @size.zero?
|
227
|
+
|
228
|
+
@lists.first.first
|
229
|
+
end
|
230
|
+
|
231
|
+
# Deletes the value at the specified index.
|
232
|
+
#
|
233
|
+
# @param index [Integer] The index of the value to delete.
|
234
|
+
def delete_at(index)
|
235
|
+
return nil if index.abs >= @size
|
236
|
+
|
237
|
+
pos, idx = pos(index)
|
238
|
+
internal_delete(pos, idx)
|
239
|
+
end
|
240
|
+
|
241
|
+
# rubocop:disable Metrics/MethodLength
|
242
|
+
|
243
|
+
# Pops the last value from the sorted array.
|
244
|
+
#
|
245
|
+
# @return [Object] The last value in the array.
|
246
|
+
def pop
|
247
|
+
return nil if @size.zero?
|
248
|
+
|
249
|
+
value = @lists.last.pop
|
250
|
+
if @lists.last.empty?
|
251
|
+
@lists.pop
|
252
|
+
@maxes.pop
|
253
|
+
@index.clear
|
254
|
+
else
|
255
|
+
@maxes[-1] = @lists.last.last
|
256
|
+
end
|
257
|
+
@size -= 1
|
258
|
+
value
|
259
|
+
end
|
260
|
+
# rubocop:enable Metrics/MethodLength
|
261
|
+
|
262
|
+
# rubocop:disable Metrics/MethodLength
|
263
|
+
|
264
|
+
# Shifts the first value from the sorted array.
|
265
|
+
#
|
266
|
+
# @return [Object] The first value in the array.
|
267
|
+
def shift
|
268
|
+
return nil if @size.zero?
|
269
|
+
|
270
|
+
value = @lists.first.shift
|
271
|
+
if @lists.first.empty?
|
272
|
+
@lists.shift
|
273
|
+
@maxes.shift
|
274
|
+
@index.clear
|
275
|
+
else
|
276
|
+
@maxes[0] = @lists.first.first
|
277
|
+
end
|
278
|
+
@size -= 1
|
279
|
+
value
|
280
|
+
end
|
281
|
+
# rubocop:enable Metrics/MethodLength
|
282
|
+
|
283
|
+
# Returns the count of elements, based on an argument or block criterion, if given.
|
284
|
+
# With no argument and no block given, returns the number of elements:
|
285
|
+
# With argument object given, returns the number of elements that are == to object:
|
286
|
+
# Uses binary search to find the first and last index of the value.
|
287
|
+
#
|
288
|
+
# @param value [Object] The value to count.
|
289
|
+
# @yield [value] The block to count with.
|
290
|
+
# @return [Integer] The count of elements.
|
291
|
+
def count(value = nil)
|
292
|
+
return super() if block_given?
|
293
|
+
return @size unless value
|
294
|
+
|
295
|
+
left_index = bisect_left(value)
|
296
|
+
right_index = bisect_right(value)
|
297
|
+
right_index - left_index
|
298
|
+
end
|
299
|
+
|
300
|
+
# Clears the sorted array, removing all values.
|
301
|
+
#
|
302
|
+
# @return [void]
|
303
|
+
def clear
|
304
|
+
@lists.clear
|
305
|
+
@maxes.clear
|
306
|
+
@index.clear
|
307
|
+
@offset = 0
|
308
|
+
@size = 0
|
309
|
+
end
|
310
|
+
|
311
|
+
# Checks if the sorted array contains a value.
|
312
|
+
#
|
313
|
+
# @param value [Object] The value to check.
|
314
|
+
# @return [Boolean] True if the value is found, false otherwise.
|
315
|
+
def include?(value)
|
316
|
+
i = internal_bisect_left(@maxes, value)
|
317
|
+
return false if i == @maxes.size
|
318
|
+
|
319
|
+
sublist = @lists[i]
|
320
|
+
idx = internal_bisect_left(sublist, value)
|
321
|
+
idx < sublist.size && sublist[idx] == value
|
322
|
+
end
|
323
|
+
|
324
|
+
# rubocop:disable Metrics/MethodLength
|
325
|
+
# rubocop:disable Metrics/AbcSize
|
326
|
+
|
327
|
+
# Updates the sorted array with values from an iterable object.
|
328
|
+
#
|
329
|
+
# @param iterable [Enumerable] The iterable object to update the array with.
|
330
|
+
def update(iterable)
|
331
|
+
# Convert the iterable to an array and sort it
|
332
|
+
values = iterable.to_a.sort
|
333
|
+
|
334
|
+
# If maxes are already defined and not empty
|
335
|
+
unless @maxes.empty?
|
336
|
+
if values.length * 4 >= @size
|
337
|
+
# If the new values are significant in number, merge all lists and re-sort
|
338
|
+
@lists << values
|
339
|
+
values = @lists.flatten.sort
|
340
|
+
clear
|
341
|
+
else
|
342
|
+
# Otherwise, add each item individually
|
343
|
+
values.each { |val| add(val) }
|
344
|
+
return
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Break sorted values into chunks of size @load_factor and extend lists
|
349
|
+
@lists += values.each_slice(@load_factor).to_a
|
350
|
+
|
351
|
+
# Update maxes based on the last element of each sublist
|
352
|
+
@maxes = @lists.map(&:last)
|
353
|
+
|
354
|
+
# Update the total length of the list
|
355
|
+
@size = values.length
|
356
|
+
|
357
|
+
# Clear the index as it might be outdated
|
358
|
+
@index.clear
|
359
|
+
end
|
360
|
+
# rubocop:enable Metrics/MethodLength
|
361
|
+
# rubocop:enable Metrics/AbcSize
|
362
|
+
|
363
|
+
# Converts the sorted array to an array.
|
364
|
+
#
|
365
|
+
# @return [Array] An array representation of the sorted array.
|
366
|
+
def to_a
|
367
|
+
@lists.flatten
|
368
|
+
end
|
369
|
+
|
370
|
+
# Array is already sorted. Duplicates the sorted array and returns it.
|
371
|
+
#
|
372
|
+
# @return [SortedArray] The sorted array.
|
373
|
+
def sort
|
374
|
+
# No need to sort, already sorted
|
375
|
+
dup
|
376
|
+
end
|
377
|
+
|
378
|
+
# Returns self, as the array is already sorted.
|
379
|
+
#
|
380
|
+
# @return [SortedArray] The sorted array.
|
381
|
+
def sort!
|
382
|
+
# No need to sort, already sorted
|
383
|
+
self
|
384
|
+
end
|
385
|
+
|
386
|
+
# Returns a new SortedArray with the same values.
|
387
|
+
#
|
388
|
+
# @return [SortedArray] The duplicated sorted array.
|
389
|
+
def dup
|
390
|
+
# Create a new instance of SortedList with the same values
|
391
|
+
new_instance = self.class.new
|
392
|
+
new_instance.lists = @lists.map(&:dup)
|
393
|
+
new_instance.maxes = @maxes.dup
|
394
|
+
new_instance.index = @index.dup
|
395
|
+
new_instance.offset = @offset
|
396
|
+
new_instance.load_factor = @load_factor
|
397
|
+
new_instance.size = @size
|
398
|
+
new_instance
|
399
|
+
end
|
400
|
+
|
401
|
+
# When non-negative, multiplies returns a new Array with each value repeated `int` times.
|
402
|
+
#
|
403
|
+
# @param num [Integer] The integer to multiply the array by.
|
404
|
+
# @return [SortedArray] The multiplied sorted array.
|
405
|
+
def multiply(num)
|
406
|
+
values = @lists.flatten * num
|
407
|
+
new_instance = self.class.new
|
408
|
+
new_instance.update(values)
|
409
|
+
new_instance
|
410
|
+
end
|
411
|
+
alias * multiply
|
412
|
+
|
413
|
+
# Returns the maximum value in the sorted array.
|
414
|
+
#
|
415
|
+
# @return [Object] The maximum value in the array.
|
416
|
+
def max
|
417
|
+
@lists.last&.last
|
418
|
+
end
|
419
|
+
|
420
|
+
# Returns the minimum value in the sorted array.
|
421
|
+
#
|
422
|
+
# @return [Object] The minimum value in the array.
|
423
|
+
def min
|
424
|
+
@lists.first&.first
|
425
|
+
end
|
426
|
+
|
427
|
+
# Iterates over each value in the sorted array.
|
428
|
+
#
|
429
|
+
# @yield [value] Gives each value to the block.
|
430
|
+
def each(&block)
|
431
|
+
@lists.each do |sublist|
|
432
|
+
sublist.each(&block)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
private
|
437
|
+
|
438
|
+
# Performs a left bisect on the array.
|
439
|
+
#
|
440
|
+
# @param array [Array] The array to bisect.
|
441
|
+
# @param value [Object] The value to bisect with.
|
442
|
+
# @return [Integer] The index where the value should be inserted.
|
443
|
+
def internal_bisect_left(array, value)
|
444
|
+
array.bsearch_index { |x| x >= value } || array.size
|
445
|
+
end
|
446
|
+
|
447
|
+
# Performs a right bisect on the array.
|
448
|
+
#
|
449
|
+
# @param array [Array] The array to bisect.
|
450
|
+
# @param value [Object] The value to bisect with.
|
451
|
+
# @return [Integer] The index where the value should be inserted.
|
452
|
+
def internal_bisect_right(array, value)
|
453
|
+
array.bsearch_index { |x| x > value } || array.length
|
454
|
+
end
|
455
|
+
|
456
|
+
# Gets the value at a given index. Supports negative indices.
|
457
|
+
#
|
458
|
+
# @param index [Integer] The index to get the value from.
|
459
|
+
def get_value_at_index(index)
|
460
|
+
return nil if index.abs >= @size
|
461
|
+
|
462
|
+
# get index from pos
|
463
|
+
index, sublist_index = pos(index)
|
464
|
+
@lists[index][sublist_index]
|
465
|
+
end
|
466
|
+
|
467
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
468
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
469
|
+
# rubocop:disable Metrics/AbcSize
|
470
|
+
# rubocop:disable Metrics/MethodLength
|
471
|
+
|
472
|
+
# Gets values from a range.
|
473
|
+
#
|
474
|
+
# @param range [Range] The range to get values from.
|
475
|
+
def get_values_from_range(range)
|
476
|
+
start = range.begin
|
477
|
+
start += @size if start.negative?
|
478
|
+
return nil if start.negative?
|
479
|
+
|
480
|
+
length = range.end
|
481
|
+
length += @size if length.negative?
|
482
|
+
length += 1 unless range.exclude_end?
|
483
|
+
length -= start
|
484
|
+
return nil if length.negative?
|
485
|
+
|
486
|
+
result = []
|
487
|
+
@lists.each do |sublist|
|
488
|
+
if start < sublist.size
|
489
|
+
result.concat(sublist[start, length])
|
490
|
+
length -= sublist.size - start
|
491
|
+
break if length <= 0
|
492
|
+
|
493
|
+
start = 0
|
494
|
+
else
|
495
|
+
start -= sublist.size
|
496
|
+
end
|
497
|
+
end
|
498
|
+
result
|
499
|
+
end
|
500
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
501
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
502
|
+
# rubocop:enable Metrics/AbcSize
|
503
|
+
# rubocop:enable Metrics/MethodLength
|
504
|
+
|
505
|
+
# rubocop:disable Metrics/MethodLength
|
506
|
+
|
507
|
+
# Gets values from an arithmetic sequence.
|
508
|
+
#
|
509
|
+
# @param sequence [Enumerator::ArithmeticSequence] The arithmetic sequence to get values from.
|
510
|
+
# @return [Array] The values from the arithmetic sequence.
|
511
|
+
def get_values_from_arithmetic_sequence(sequence)
|
512
|
+
result = []
|
513
|
+
sequence.each do |index|
|
514
|
+
break if index.negative? || index >= @size
|
515
|
+
|
516
|
+
@lists.each do |sublist|
|
517
|
+
if index < sublist.size
|
518
|
+
result << sublist[index]
|
519
|
+
break
|
520
|
+
else
|
521
|
+
index -= sublist.size
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
result
|
526
|
+
end
|
527
|
+
# rubocop:enable Metrics/MethodLength
|
528
|
+
|
529
|
+
# rubocop:disable Metrics/MethodLength
|
530
|
+
|
531
|
+
# Gets values starting from a given index and continuing for a given length.
|
532
|
+
# Supports negative indices.
|
533
|
+
#
|
534
|
+
# @param start [Integer] The index to start from.
|
535
|
+
# @param length [Integer] The length of the values to get.
|
536
|
+
# @return [Array] The values starting from the given index and continuing for the given length.
|
537
|
+
def get_values_from_start_and_length(start, length)
|
538
|
+
return nil if start >= @size
|
539
|
+
|
540
|
+
if length.negative?
|
541
|
+
nil
|
542
|
+
else
|
543
|
+
if start.negative?
|
544
|
+
start += @size
|
545
|
+
return nil if start.negative?
|
546
|
+
end
|
547
|
+
|
548
|
+
result = []
|
549
|
+
while length.positive?
|
550
|
+
break if start >= @size
|
551
|
+
|
552
|
+
loc, idx = pos(start)
|
553
|
+
start += 1
|
554
|
+
length -= 1
|
555
|
+
result << @lists[loc][idx]
|
556
|
+
end
|
557
|
+
end
|
558
|
+
result
|
559
|
+
end
|
560
|
+
# rubocop:enable Metrics/MethodLength
|
561
|
+
|
562
|
+
# rubocop:disable Metrics/AbcSize
|
563
|
+
# rubocop:disable Metrics/MethodLength
|
564
|
+
|
565
|
+
# Expands a sublist if it exceeds the load factor.
|
566
|
+
#
|
567
|
+
# @param pos [Integer] The index of the sublist to expand.
|
568
|
+
def expand(pos)
|
569
|
+
if @lists[pos].size > (@load_factor << 1)
|
570
|
+
half = @lists[pos].slice!(@load_factor, @lists[pos].size - @load_factor)
|
571
|
+
@maxes[pos] = @lists[pos].last
|
572
|
+
@lists.insert(pos + 1, half)
|
573
|
+
@maxes.insert(pos + 1, half.last)
|
574
|
+
@index.clear
|
575
|
+
elsif @index.size.positive?
|
576
|
+
child = @offset + pos
|
577
|
+
while child.positive?
|
578
|
+
@index[child] += 1
|
579
|
+
child = (child - 1) >> 1
|
580
|
+
end
|
581
|
+
@index[0] += 1
|
582
|
+
end
|
583
|
+
end
|
584
|
+
# rubocop:enable Metrics/AbcSize
|
585
|
+
# rubocop:enable Metrics/MethodLength
|
586
|
+
|
587
|
+
# rubocop:disable Metrics/AbcSize
|
588
|
+
# rubocop:disable Metrics/MethodLength
|
589
|
+
|
590
|
+
# Deletes a value from a sublist.
|
591
|
+
#
|
592
|
+
# @param pos [Integer] The index of the sublist.
|
593
|
+
# @param idx [Integer] The index of the value to delete.
|
594
|
+
# @return [Object] The value that was deleted.
|
595
|
+
def internal_delete(pos, idx)
|
596
|
+
list = @lists[pos]
|
597
|
+
value = list.delete_at(idx)
|
598
|
+
@size -= 1
|
599
|
+
|
600
|
+
len_list = list.length
|
601
|
+
|
602
|
+
if len_list > (@load_factor >> 1)
|
603
|
+
@maxes[pos] = list.last
|
604
|
+
|
605
|
+
if @index.size.positive?
|
606
|
+
child = @offset + pos
|
607
|
+
while child.positive?
|
608
|
+
@index[child] -= 1
|
609
|
+
child = (child - 1) >> 1
|
610
|
+
end
|
611
|
+
@index[0] -= 1
|
612
|
+
end
|
613
|
+
elsif @lists.length > 1
|
614
|
+
pos += 1 if pos.zero?
|
615
|
+
|
616
|
+
prev = pos - 1
|
617
|
+
@lists[prev].concat(list)
|
618
|
+
@maxes[prev] = @lists[prev].last
|
619
|
+
|
620
|
+
@lists.delete_at(pos)
|
621
|
+
@maxes.delete_at(pos)
|
622
|
+
@index.clear
|
623
|
+
|
624
|
+
expand(prev)
|
625
|
+
elsif len_list.positive?
|
626
|
+
@maxes[pos] = list.last
|
627
|
+
else
|
628
|
+
@lists.delete_at(pos)
|
629
|
+
@maxes.delete_at(pos)
|
630
|
+
@index.clear
|
631
|
+
end
|
632
|
+
value
|
633
|
+
end
|
634
|
+
# rubocop:enable Metrics/AbcSize
|
635
|
+
# rubocop:enable Metrics/MethodLength
|
636
|
+
|
637
|
+
# rubocop:disable Metrics/AbcSize
|
638
|
+
# rubocop:disable Metrics/MethodLength
|
639
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
640
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
641
|
+
|
642
|
+
# Builds the positional index for indexing the sorted array.
|
643
|
+
# Indexes are represented as binary trees in a dense array notation
|
644
|
+
# similar to a binary heap.
|
645
|
+
#
|
646
|
+
# For example, given a lists representation storing integers:
|
647
|
+
#
|
648
|
+
# 0: [1, 2, 3]
|
649
|
+
# 1: [4, 5]
|
650
|
+
# 2: [6, 7, 8, 9]
|
651
|
+
# 3: [10, 11, 12, 13, 14]
|
652
|
+
#
|
653
|
+
# The first transformation maps the sub-lists by their length. The
|
654
|
+
# first row of the index is the length of the sub-lists:
|
655
|
+
#
|
656
|
+
# 0: [3, 2, 4, 5]
|
657
|
+
#
|
658
|
+
# Each row after that is the sum of consecutive pairs of the previous
|
659
|
+
# row:
|
660
|
+
#
|
661
|
+
# 1: [5, 9]
|
662
|
+
# 2: [14]
|
663
|
+
#
|
664
|
+
# Finally, the index is built by concatenating these lists together:
|
665
|
+
#
|
666
|
+
# @index = [14, 5, 9, 3, 2, 4, 5]
|
667
|
+
#
|
668
|
+
# An offset storing the start of the first row is also stored:
|
669
|
+
#
|
670
|
+
# @offset = 3
|
671
|
+
#
|
672
|
+
# When built, the index can be used for efficient indexing into the list.
|
673
|
+
# See the comment and notes on `SortedArray#pos` for details.
|
674
|
+
def build_index
|
675
|
+
# Build initial row from the lengths of each sublist
|
676
|
+
row0 = @lists.map(&:length)
|
677
|
+
|
678
|
+
# Early return if there is only one sublist
|
679
|
+
if row0.length == 1
|
680
|
+
@index = row0
|
681
|
+
@offset = 0
|
682
|
+
return
|
683
|
+
end
|
684
|
+
|
685
|
+
# Build the first row by summing consecutive pairs
|
686
|
+
# discard the last element if the row is odd
|
687
|
+
row1 = row0.each_slice(2).map { |a, b| a + b if b }.compact
|
688
|
+
|
689
|
+
# Handle odd number of elements in row0
|
690
|
+
row1 << row0[-1] if row0.length.odd?
|
691
|
+
|
692
|
+
# Return early if only one row is needed
|
693
|
+
if row1.length == 1
|
694
|
+
@index = row1 + row0
|
695
|
+
@offset = 1
|
696
|
+
return
|
697
|
+
end
|
698
|
+
|
699
|
+
# Calculate the size for a complete binary tree
|
700
|
+
the_size = 2**(Math.log2(row1.length - 1).to_i + 1)
|
701
|
+
row1 += [0] * (the_size - row1.length)
|
702
|
+
tree = [row0, row1]
|
703
|
+
|
704
|
+
while tree.last.length > 1
|
705
|
+
row = []
|
706
|
+
tree.last.each_slice(2) { |a, b| row << (a + b) }
|
707
|
+
tree << row
|
708
|
+
end
|
709
|
+
|
710
|
+
# Flatten the tree into the index array
|
711
|
+
tree.reverse_each { |level| @index.concat(level) }
|
712
|
+
@offset = (the_size * 2) - 1
|
713
|
+
end
|
714
|
+
# rubocop:enable Metrics/AbcSize
|
715
|
+
# rubocop:enable Metrics/MethodLength
|
716
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
717
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
718
|
+
|
719
|
+
# rubocop:disable Metrics/AbcSize
|
720
|
+
# rubocop:disable Metrics/MethodLength
|
721
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
722
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
723
|
+
|
724
|
+
# Convert an index into an index pair (lists index, sublist index)
|
725
|
+
# that can be used to access the corresponding lists position.
|
726
|
+
#
|
727
|
+
# Many queries require the index be built. Details of the index are
|
728
|
+
# described in `SortedArray#build_index`.
|
729
|
+
#
|
730
|
+
# Indexing requires traversing the tree to a leaf node. Each node has two
|
731
|
+
# children which are easily computable. Given an index, pos, the
|
732
|
+
# left-child is at `pos * 2 + 1` and the right-child is at `pos * 2 + 2`.
|
733
|
+
#
|
734
|
+
# When the index is less than the left-child, traversal moves to the
|
735
|
+
# left sub-tree. Otherwise, the index is decremented by the left-child
|
736
|
+
# and traversal moves to the right sub-tree.
|
737
|
+
#
|
738
|
+
# At a child node, the indexing pair is computed from the relative
|
739
|
+
# position of the child node as compared with the offset and the remaining
|
740
|
+
# index.
|
741
|
+
#
|
742
|
+
# For example, using the index from `SortedArray#build_index`:
|
743
|
+
#
|
744
|
+
# index = 14 5 9 3 2 4 5
|
745
|
+
# offset = 3
|
746
|
+
#
|
747
|
+
# Tree:
|
748
|
+
#
|
749
|
+
# 14
|
750
|
+
# 5 9
|
751
|
+
# 3 2 4 5
|
752
|
+
#
|
753
|
+
# Indexing position 8 involves iterating like so:
|
754
|
+
#
|
755
|
+
# 1. Starting at the root, position 0, 8 is compared with the left-child
|
756
|
+
# node (5) which it is greater than. When greater the index is
|
757
|
+
# decremented and the position is updated to the right child node.
|
758
|
+
#
|
759
|
+
# 2. At node 9 with index 3, we again compare the index to the left-child
|
760
|
+
# node with value 4. Because the index is the less than the left-child
|
761
|
+
# node, we simply traverse to the left.
|
762
|
+
#
|
763
|
+
# 3. At node 4 with index 3, we recognize that we are at a leaf node and
|
764
|
+
# stop iterating.
|
765
|
+
#
|
766
|
+
# To compute the sublist index, we subtract the offset from the index
|
767
|
+
# of the leaf node: 5 - 3 = 2. To compute the index in the sublist, we
|
768
|
+
# simply use the index remaining from iteration. In this case, 3.
|
769
|
+
#
|
770
|
+
# The final index pair from our example is (2, 3) which corresponds to
|
771
|
+
# index 8 in the sorted list.
|
772
|
+
#
|
773
|
+
# @param idx [Integer] The index in the sorted list.
|
774
|
+
# @return [Array] The (lists index, sublist index) pair.
|
775
|
+
def pos(idx)
|
776
|
+
if idx.negative?
|
777
|
+
last_len = @lists[-1].size
|
778
|
+
|
779
|
+
return @lists.size - 1, last_len + idx if (-idx) <= last_len
|
780
|
+
|
781
|
+
idx += @size
|
782
|
+
|
783
|
+
raise IndexError, "list index out of range" if idx.negative?
|
784
|
+
|
785
|
+
elsif idx >= @size
|
786
|
+
raise IndexError, "list index out of range"
|
787
|
+
end
|
788
|
+
|
789
|
+
return 0, idx if idx < @lists[0].size
|
790
|
+
|
791
|
+
build_index if @index.empty?
|
792
|
+
|
793
|
+
pos = 0
|
794
|
+
child = 1
|
795
|
+
len_index = @index.size
|
796
|
+
|
797
|
+
while child < len_index
|
798
|
+
index_child = @index[child]
|
799
|
+
|
800
|
+
if idx < index_child
|
801
|
+
pos = child
|
802
|
+
else
|
803
|
+
idx -= index_child
|
804
|
+
|
805
|
+
pos = child + 1
|
806
|
+
end
|
807
|
+
|
808
|
+
child = (pos << 1) + 1
|
809
|
+
end
|
810
|
+
|
811
|
+
[pos - @offset, idx]
|
812
|
+
end
|
813
|
+
|
814
|
+
# rubocop:enable Metrics/AbcSize
|
815
|
+
# rubocop:enable Metrics/MethodLength
|
816
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
817
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
818
|
+
|
819
|
+
# Turns a position and index into an absolute index.
|
820
|
+
#
|
821
|
+
# @param pos [Integer] The position in the index.
|
822
|
+
# @param idx [Integer] The index in the sublist.
|
823
|
+
def loc(pos, idx)
|
824
|
+
return idx if pos.zero?
|
825
|
+
|
826
|
+
build_index if @index.empty?
|
827
|
+
|
828
|
+
# Increment pos to point in the index to @lists[pos].size.
|
829
|
+
total = 0
|
830
|
+
|
831
|
+
pos += @offset
|
832
|
+
|
833
|
+
# Iterate until reaching the root of the index tree at pos = 0.
|
834
|
+
while pos.positive?
|
835
|
+
|
836
|
+
# Right-child nodes are at even indices. At such indices
|
837
|
+
# account the total below the left child node.
|
838
|
+
total += @index[pos - 1] if pos.even?
|
839
|
+
|
840
|
+
# Advance pos to the parent node.
|
841
|
+
pos = (pos - 1) >> 1
|
842
|
+
end
|
843
|
+
|
844
|
+
total + idx
|
845
|
+
end
|
846
|
+
|
847
|
+
protected
|
848
|
+
|
849
|
+
attr_accessor :lists, :maxes, :index, :offset, :load_factor
|
850
|
+
|
851
|
+
attr_writer :size
|
852
|
+
end
|
853
|
+
end
|
854
|
+
# rubocop:enable Metrics/ClassLength
|