Skip to content

Commit

Permalink
🐛 Fix SequenceSet count dups with multiple "*"
Browse files Browse the repository at this point in the history
In `#count`, "*" is treated as if it is effectively UINT32_MAX.  That
was also the intention for `#count_with_duplicates`.

Unlike `#count`, which can assume that `*` only appears at most once,
`#count_with_duplicates` needs to check each entry.

This means that, e.g:
   SequenceSet["#{UINT32_MAX}:*"].count_with_duplicates == 1
   SequenceSet["#{UINT32_MAX},*"].count_with_duplicates == 2
  • Loading branch information
nevans committed Jan 27, 2025
1 parent a2cc018 commit b4cc112
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 10 deletions.
24 changes: 14 additions & 10 deletions lib/net/imap/sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1033,26 +1033,35 @@ def to_set; Set.new(numbers) end

# Returns the count of #numbers in the set.
#
# If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
# integer value) are both in the set, they will only be counted once.
# <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
# unsigned integer value).
#
# Related: #count_with_duplicates
def count
count_numbers_in_tuples(@tuples)
@tuples.sum(@tuples.count) { _2 - _1 } +
(include_star? && include?(UINT32_MAX) ? -1 : 0)
end

alias size count

# Returns the count of numbers in the ordered #entries, including any
# repeated numbers.
#
# <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
# unsigned integer value).
#
# When #string is normalized, this behaves the same as #count.
#
# Related: #entries, #count_duplicates, #has_duplicates?
def count_with_duplicates
return count unless @string
count_numbers_in_tuples(each_entry_tuple)
each_entry_tuple.sum {|min, max|
max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
}
end

# Returns the count of repeated numbers in the ordered #entries.
# Returns the count of repeated numbers in the ordered #entries, the
# difference between #count_with_duplicates and #count.
#
# When #string is normalized, this is zero.
#
Expand All @@ -1074,11 +1083,6 @@ def has_duplicates?
count_with_duplicates != count
end

private def count_numbers_in_tuples(tuples)
tuples.sum(tuples.count) { _2 - _1 } +
(include_star? && include?(UINT32_MAX) ? -1 : 0)
end

# Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
# the set.
#
Expand Down
13 changes: 13 additions & 0 deletions test/net/imap/test_sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,19 @@ def test_inspect((expected, input, freeze))
complement: "6:8,12:*",
}, keep: true

data "multiple *", {
input: "2:*,3:*,*",
elements: [2..],
entries: [2.., 3.., :*],
ranges: [2..],
numbers: RangeError,
to_s: "2:*,3:*,*",
normalize: "2:*",
count: 2**32 - 2,
count_dups: 2**32 - 2,
complement: "1",
}, keep: true

data "array", {
input: ["1:5,3:4", 9..11, "10", 99, :*],
elements: [1..5, 9..11, 99, :*],
Expand Down

0 comments on commit b4cc112

Please sign in to comment.