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