Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add SequenceSet#find_ordered_index #396

Merged
merged 2 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions lib/net/imap/sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,15 @@ class IMAP
# - #max: Returns the maximum number in the set.
# - #minmax: Returns the minimum and maximum numbers in the set.
#
# <i>Accessing value by (normalized) offset:</i>
# <i>Accessing value by offset in sorted set:</i>
# - #[] (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.
#
# <i>Accessing value by offset in ordered entries</i>
# - #find_ordered_index: Returns the index of the given number's first
# occurrence in entries.
#
# <i>Set cardinality:</i>
# - #count (aliased as #size): Returns the count of numbers in the set.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -1168,13 +1191,14 @@ def at(index)
# set[11] #=> nil
# set[-12] #=> nil
#
# The result is based on the normalized set—sorted and de-duplicatednot
# 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)
Expand Down
38 changes: 38 additions & 0 deletions test/net/imap/test_sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down