Skip to content

Commit 436bd1c

Browse files
authored
🔀 Merge pull request #396 from ruby/sequence_set-find_ordered_index
✨ Add `SequenceSet#find_ordered_index`
2 parents d51d12e + 5e69ce1 commit 436bd1c

File tree

2 files changed

+84
-22
lines changed

2 files changed

+84
-22
lines changed

‎lib/net/imap/sequence_set.rb

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,15 @@ class IMAP
183183
# - #max: Returns the maximum number in the set.
184184
# - #minmax: Returns the minimum and maximum numbers in the set.
185185
#
186-
# <i>Accessing value by (normalized) offset:</i>
186+
# <i>Accessing value by offset in sorted set:</i>
187187
# - #[] (aliased as #slice): Returns the number or consecutive subset at a
188-
# given offset or range of offsets.
189-
# - #at: Returns the number at a given offset.
190-
# - #find_index: Returns the given number's offset in the set
188+
# given offset or range of offsets in the sorted set.
189+
# - #at: Returns the number at a given offset in the sorted set.
190+
# - #find_index: Returns the given number's offset in the sorted set.
191+
#
192+
# <i>Accessing value by offset in ordered entries</i>
193+
# - #find_ordered_index: Returns the index of the given number's first
194+
# occurrence in entries.
191195
#
192196
# <i>Set cardinality:</i>
193197
# - #count (aliased as #size): Returns the count of numbers in the set.
@@ -1083,33 +1087,48 @@ def has_duplicates?
10831087
count_with_duplicates != count
10841088
end
10851089

1086-
# Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
1087-
# the set.
1090+
# Returns the (sorted and deduplicated) index of +number+ in the set, or
1091+
# +nil+ if +number+ isn't in the set.
10881092
#
1089-
# Related: #[]
1093+
# Related: #[], #at, #find_ordered_index
10901094
def find_index(number)
10911095
number = to_tuple_int number
1092-
each_tuple_with_index do |min, max, idx_min|
1096+
each_tuple_with_index(@tuples) do |min, max, idx_min|
10931097
number < min and return nil
10941098
number <= max and return from_tuple_int(idx_min + (number - min))
10951099
end
10961100
nil
10971101
end
10981102

1103+
# Returns the first index of +number+ in the ordered #entries, or
1104+
# +nil+ if +number+ isn't in the set.
1105+
#
1106+
# Related: #find_index
1107+
def find_ordered_index(number)
1108+
number = to_tuple_int number
1109+
each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1110+
if min <= number && number <= max
1111+
return from_tuple_int(idx_min + (number - min))
1112+
end
1113+
end
1114+
nil
1115+
end
1116+
10991117
private
11001118

1101-
def each_tuple_with_index
1119+
def each_tuple_with_index(tuples)
11021120
idx_min = 0
1103-
@tuples.each do |min, max|
1104-
yield min, max, idx_min, (idx_max = idx_min + (max - min))
1121+
tuples.each do |min, max|
1122+
idx_max = idx_min + (max - min)
1123+
yield min, max, idx_min, idx_max
11051124
idx_min = idx_max + 1
11061125
end
11071126
idx_min
11081127
end
11091128

1110-
def reverse_each_tuple_with_index
1129+
def reverse_each_tuple_with_index(tuples)
11111130
idx_max = -1
1112-
@tuples.reverse_each do |min, max|
1131+
tuples.reverse_each do |min, max|
11131132
yield min, max, (idx_min = idx_max - (max - min)), idx_max
11141133
idx_max = idx_min - 1
11151134
end
@@ -1120,18 +1139,21 @@ def reverse_each_tuple_with_index
11201139

11211140
# :call-seq: at(index) -> integer or nil
11221141
#
1123-
# Returns a number from +self+, without modifying the set. Behaves the
1124-
# same as #[], except that #at only allows a single integer argument.
1142+
# Returns the number at the given +index+ in the sorted set, without
1143+
# modifying the set.
1144+
#
1145+
# +index+ is interpreted the same as in #[], except that #at only allows a
1146+
# single integer argument.
11251147
#
11261148
# Related: #[], #slice
11271149
def at(index)
11281150
index = Integer(index.to_int)
11291151
if index.negative?
1130-
reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
1152+
reverse_each_tuple_with_index(@tuples) do |min, max, idx_min, idx_max|
11311153
idx_min <= index and return from_tuple_int(min + (index - idx_min))
11321154
end
11331155
else
1134-
each_tuple_with_index do |min, _, idx_min, idx_max|
1156+
each_tuple_with_index(@tuples) do |min, _, idx_min, idx_max|
11351157
index <= idx_max and return from_tuple_int(min + (index - idx_min))
11361158
end
11371159
end
@@ -1146,17 +1168,18 @@ def at(index)
11461168
# seqset[range] -> sequence set or nil
11471169
# slice(range) -> sequence set or nil
11481170
#
1149-
# Returns a number or a subset from +self+, without modifying the set.
1171+
# Returns a number or a subset from the _sorted_ set, without modifying
1172+
# the set.
11501173
#
11511174
# When an Integer argument +index+ is given, the number at offset +index+
1152-
# is returned:
1175+
# in the sorted set is returned:
11531176
#
11541177
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
11551178
# set[0] #=> 10
11561179
# set[5] #=> 15
11571180
# set[10] #=> 26
11581181
#
1159-
# If +index+ is negative, it counts relative to the end of +self+:
1182+
# If +index+ is negative, it counts relative to the end of the sorted set:
11601183
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
11611184
# set[-1] #=> 26
11621185
# set[-3] #=> 22
@@ -1168,13 +1191,14 @@ def at(index)
11681191
# set[11] #=> nil
11691192
# set[-12] #=> nil
11701193
#
1171-
# The result is based on the normalized set—sorted and de-duplicated—not
1172-
# on the assigned value of #string.
1194+
# The result is based on the sorted and de-duplicated set, not on the
1195+
# ordered #entries in #string.
11731196
#
11741197
# set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
11751198
# set[0] #=> 11
11761199
# set[-1] #=> 23
11771200
#
1201+
# Related: #at
11781202
def [](index, length = nil)
11791203
if length then slice_length(index, length)
11801204
elsif index.is_a?(Range) then slice_range(index)

‎test/net/imap/test_sequence_set.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,44 @@ def obj.to_sequence_set; 192_168.001_255 end
251251
assert_equal 2**32 - 1, SequenceSet.full.find_index(:*)
252252
end
253253

254+
test "#find_ordered_index" do
255+
assert_equal 9, SequenceSet.full.find_ordered_index(10)
256+
assert_equal 99, SequenceSet.full.find_ordered_index(100)
257+
assert_equal 2**32 - 1, SequenceSet.full.find_ordered_index(:*)
258+
assert_nil SequenceSet.empty.find_index(1)
259+
set = SequenceSet["9,8,7,6,5,4,3,2,1"]
260+
assert_equal 0, set.find_ordered_index(9)
261+
assert_equal 1, set.find_ordered_index(8)
262+
assert_equal 2, set.find_ordered_index(7)
263+
assert_equal 3, set.find_ordered_index(6)
264+
assert_equal 4, set.find_ordered_index(5)
265+
assert_equal 5, set.find_ordered_index(4)
266+
assert_equal 6, set.find_ordered_index(3)
267+
assert_equal 7, set.find_ordered_index(2)
268+
assert_equal 8, set.find_ordered_index(1)
269+
assert_nil set.find_ordered_index(10)
270+
set = SequenceSet["7:9,5:6"]
271+
assert_equal 0, set.find_ordered_index(7)
272+
assert_equal 1, set.find_ordered_index(8)
273+
assert_equal 2, set.find_ordered_index(9)
274+
assert_equal 3, set.find_ordered_index(5)
275+
assert_equal 4, set.find_ordered_index(6)
276+
assert_nil set.find_ordered_index(4)
277+
set = SequenceSet["1000:1111,1:100"]
278+
assert_equal 0, set.find_ordered_index(1000)
279+
assert_equal 100, set.find_ordered_index(1100)
280+
assert_equal 112, set.find_ordered_index(1)
281+
assert_equal 121, set.find_ordered_index(10)
282+
set = SequenceSet["1,1,1,1,51,50,4,11"]
283+
assert_equal 0, set.find_ordered_index(1)
284+
assert_equal 4, set.find_ordered_index(51)
285+
assert_equal 5, set.find_ordered_index(50)
286+
assert_equal 6, set.find_ordered_index(4)
287+
assert_equal 7, set.find_ordered_index(11)
288+
assert_equal 1, SequenceSet["1,*"].find_ordered_index(-1)
289+
assert_equal 0, SequenceSet["*,1"].find_ordered_index(-1)
290+
end
291+
254292
test "#limit" do
255293
set = SequenceSet["1:100,500"]
256294
assert_equal [1..99], set.limit(max: 99).ranges

0 commit comments

Comments
 (0)