diff --git a/lib/net/imap/sequence_set.rb b/lib/net/imap/sequence_set.rb
index 1d09aa05..a8091c05 100644
--- a/lib/net/imap/sequence_set.rb
+++ b/lib/net/imap/sequence_set.rb
@@ -183,11 +183,15 @@ class IMAP
# - #max: Returns the maximum number in the set.
# - #minmax: Returns the minimum and maximum numbers in the set.
#
- # Accessing value by (normalized) offset:
+ # Accessing value by offset in sorted set:
# - #[] (aliased as #slice): Returns the number or consecutive subset at a
- # given offset or range of offsets.
- # - #at: Returns the number at a given offset.
- # - #find_index: Returns the given number's offset in the set
+ # given offset or range of offsets in the sorted set.
+ # - #at: Returns the number at a given offset in the sorted set.
+ # - #find_index: Returns the given number's offset in the sorted set.
+ #
+ # Accessing value by offset in ordered entries
+ # - #find_ordered_index: Returns the index of the given number's first
+ # occurrence in entries.
#
# Set cardinality:
# - #count (aliased as #size): Returns the count of numbers in the set.
@@ -1083,33 +1087,48 @@ def has_duplicates?
count_with_duplicates != count
end
- # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
- # the set.
+ # Returns the (sorted and deduplicated) index of +number+ in the set, or
+ # +nil+ if +number+ isn't in the set.
#
- # Related: #[]
+ # Related: #[], #at, #find_ordered_index
def find_index(number)
number = to_tuple_int number
- each_tuple_with_index do |min, max, idx_min|
+ each_tuple_with_index(@tuples) do |min, max, idx_min|
number < min and return nil
number <= max and return from_tuple_int(idx_min + (number - min))
end
nil
end
+ # Returns the first index of +number+ in the ordered #entries, or
+ # +nil+ if +number+ isn't in the set.
+ #
+ # Related: #find_index
+ def find_ordered_index(number)
+ number = to_tuple_int number
+ each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
+ if min <= number && number <= max
+ return from_tuple_int(idx_min + (number - min))
+ end
+ end
+ nil
+ end
+
private
- def each_tuple_with_index
+ def each_tuple_with_index(tuples)
idx_min = 0
- @tuples.each do |min, max|
- yield min, max, idx_min, (idx_max = idx_min + (max - min))
+ tuples.each do |min, max|
+ idx_max = idx_min + (max - min)
+ yield min, max, idx_min, idx_max
idx_min = idx_max + 1
end
idx_min
end
- def reverse_each_tuple_with_index
+ def reverse_each_tuple_with_index(tuples)
idx_max = -1
- @tuples.reverse_each do |min, max|
+ tuples.reverse_each do |min, max|
yield min, max, (idx_min = idx_max - (max - min)), idx_max
idx_max = idx_min - 1
end
@@ -1120,18 +1139,21 @@ def reverse_each_tuple_with_index
# :call-seq: at(index) -> integer or nil
#
- # Returns a number from +self+, without modifying the set. Behaves the
- # same as #[], except that #at only allows a single integer argument.
+ # Returns the number at the given +index+ in the sorted set, without
+ # modifying the set.
+ #
+ # +index+ is interpreted the same as in #[], except that #at only allows a
+ # single integer argument.
#
# Related: #[], #slice
def at(index)
index = Integer(index.to_int)
if index.negative?
- reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
+ reverse_each_tuple_with_index(@tuples) do |min, max, idx_min, idx_max|
idx_min <= index and return from_tuple_int(min + (index - idx_min))
end
else
- each_tuple_with_index do |min, _, idx_min, idx_max|
+ each_tuple_with_index(@tuples) do |min, _, idx_min, idx_max|
index <= idx_max and return from_tuple_int(min + (index - idx_min))
end
end
@@ -1146,17 +1168,18 @@ def at(index)
# seqset[range] -> sequence set or nil
# slice(range) -> sequence set or nil
#
- # Returns a number or a subset from +self+, without modifying the set.
+ # Returns a number or a subset from the _sorted_ set, without modifying
+ # the set.
#
# When an Integer argument +index+ is given, the number at offset +index+
- # is returned:
+ # in the sorted set is returned:
#
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
# set[0] #=> 10
# set[5] #=> 15
# set[10] #=> 26
#
- # If +index+ is negative, it counts relative to the end of +self+:
+ # If +index+ is negative, it counts relative to the end of the sorted set:
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
# set[-1] #=> 26
# set[-3] #=> 22
@@ -1168,13 +1191,14 @@ def at(index)
# set[11] #=> nil
# set[-12] #=> nil
#
- # The result is based on the normalized set—sorted and de-duplicated—not
- # on the assigned value of #string.
+ # The result is based on the sorted and de-duplicated set, not on the
+ # ordered #entries in #string.
#
# set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
# set[0] #=> 11
# set[-1] #=> 23
#
+ # Related: #at
def [](index, length = nil)
if length then slice_length(index, length)
elsif index.is_a?(Range) then slice_range(index)
diff --git a/test/net/imap/test_sequence_set.rb b/test/net/imap/test_sequence_set.rb
index b6995305..58aefae5 100644
--- a/test/net/imap/test_sequence_set.rb
+++ b/test/net/imap/test_sequence_set.rb
@@ -251,6 +251,44 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_equal 2**32 - 1, SequenceSet.full.find_index(:*)
end
+ test "#find_ordered_index" do
+ assert_equal 9, SequenceSet.full.find_ordered_index(10)
+ assert_equal 99, SequenceSet.full.find_ordered_index(100)
+ assert_equal 2**32 - 1, SequenceSet.full.find_ordered_index(:*)
+ assert_nil SequenceSet.empty.find_index(1)
+ set = SequenceSet["9,8,7,6,5,4,3,2,1"]
+ assert_equal 0, set.find_ordered_index(9)
+ assert_equal 1, set.find_ordered_index(8)
+ assert_equal 2, set.find_ordered_index(7)
+ assert_equal 3, set.find_ordered_index(6)
+ assert_equal 4, set.find_ordered_index(5)
+ assert_equal 5, set.find_ordered_index(4)
+ assert_equal 6, set.find_ordered_index(3)
+ assert_equal 7, set.find_ordered_index(2)
+ assert_equal 8, set.find_ordered_index(1)
+ assert_nil set.find_ordered_index(10)
+ set = SequenceSet["7:9,5:6"]
+ assert_equal 0, set.find_ordered_index(7)
+ assert_equal 1, set.find_ordered_index(8)
+ assert_equal 2, set.find_ordered_index(9)
+ assert_equal 3, set.find_ordered_index(5)
+ assert_equal 4, set.find_ordered_index(6)
+ assert_nil set.find_ordered_index(4)
+ set = SequenceSet["1000:1111,1:100"]
+ assert_equal 0, set.find_ordered_index(1000)
+ assert_equal 100, set.find_ordered_index(1100)
+ assert_equal 112, set.find_ordered_index(1)
+ assert_equal 121, set.find_ordered_index(10)
+ set = SequenceSet["1,1,1,1,51,50,4,11"]
+ assert_equal 0, set.find_ordered_index(1)
+ assert_equal 4, set.find_ordered_index(51)
+ assert_equal 5, set.find_ordered_index(50)
+ assert_equal 6, set.find_ordered_index(4)
+ assert_equal 7, set.find_ordered_index(11)
+ assert_equal 1, SequenceSet["1,*"].find_ordered_index(-1)
+ assert_equal 0, SequenceSet["*,1"].find_ordered_index(-1)
+ end
+
test "#limit" do
set = SequenceSet["1:100,500"]
assert_equal [1..99], set.limit(max: 99).ranges