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.
@@ -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