sorted_containers 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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