Skip to content

Commit ac4699f

Browse files
committed
✨ Add SequenceSet#cardinality method
Unlike `#count`, `#cardinality` handles `*` the way you would expect a set to handle it, as its own distinct member.
1 parent 7d73944 commit ac4699f

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

lib/net/imap/sequence_set.rb

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,10 @@ class IMAP
275275
# occurrence in entries.
276276
#
277277
# <i>Set cardinality:</i>
278-
# - #count (aliased as #size): Returns the count of numbers in the set.
279-
# Duplicated numbers are not counted.
278+
# - #cardinality: Returns the number of distinct members in the set.
279+
# <tt>*</tt> is counted as its own entry, distinct from UINT32_MAX.
280+
# - #count (aliased as #size): Returns the count of distinct numbers in the
281+
# set. <tt>*</tt> is counted as UINT32_MAX.
280282
# - #empty?: Returns whether the set has no members. \IMAP syntax does not
281283
# allow empty sequence sets.
282284
# - #valid?: Returns whether the set has any members.
@@ -1336,28 +1338,71 @@ def each_ordered_number(&block)
13361338
# Related: #elements, #ranges, #numbers
13371339
def to_set; Set.new(numbers) end
13381340

1341+
# Returns the number of members in the set.
1342+
#
1343+
# Unlike #count, <tt>"*"</tt> is considered to be distinct from
1344+
# <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1345+
#
1346+
# set = Net::IMAP::SequenceSet[1..10]
1347+
# set.count #=> 10
1348+
# set.cardinality #=> 10
1349+
#
1350+
# set = Net::IMAP::SequenceSet["4294967295,*"]
1351+
# set.count #=> 1
1352+
# set.cardinality #=> 2
1353+
#
1354+
# set = Net::IMAP::SequenceSet[1..]
1355+
# set.count #=> 4294967295
1356+
# set.cardinality #=> 4294967296
1357+
#
1358+
# Related: #count, #count_with_duplicates
1359+
def cardinality = minmaxes.sum(@set_data.count) { _2 - _1 }
1360+
13391361
# Returns the count of #numbers in the set.
13401362
#
1341-
# <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1342-
# unsigned integer value).
1363+
# Unlike #cardinality, <tt>"*"</tt> is considered to be equal to
1364+
# <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1365+
#
1366+
# set = Net::IMAP::SequenceSet[1..10]
1367+
# set.count #=> 10
1368+
# set.cardinality #=> 10
1369+
#
1370+
# set = Net::IMAP::SequenceSet["4294967295,*"]
1371+
# set.count #=> 1
1372+
# set.cardinality #=> 2
13431373
#
1344-
# Related: #count_with_duplicates
1374+
# set = Net::IMAP::SequenceSet[1..]
1375+
# set.count #=> 4294967295
1376+
# set.cardinality #=> 4294967296
1377+
#
1378+
# Related: #cardinality, #count_with_duplicates
13451379
def count
1346-
minmaxes.sum(minmaxes.count) { _2 - _1 } +
1347-
(include_star? && include?(UINT32_MAX) ? -1 : 0)
1380+
cardinality + (include_star? && include?(UINT32_MAX) ? -1 : 0)
13481381
end
13491382

13501383
alias size count
13511384

13521385
# Returns the count of numbers in the ordered #entries, including any
13531386
# repeated numbers.
13541387
#
1355-
# <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1356-
# unsigned integer value).
1388+
# When #string is normalized, this returns the same as #count.
1389+
# Like #count, <tt>"*"</tt> is be considered to be equal to
1390+
# <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1391+
#
1392+
# In a range, <tt>"*"</tt> is _not_ considered a duplicate:
1393+
# set = Net::IMAP::SequenceSet["4294967295:*"]
1394+
# set.count_with_duplicates #=> 1
1395+
# set.count #=> 1
1396+
# set.cardinality #=> 2
13571397
#
1358-
# When #string is normalized, this behaves the same as #count.
1398+
# In a separate entry, <tt>"*"</tt> _is_ considered a duplicate:
1399+
# set = Net::IMAP::SequenceSet["4294967295,*"]
1400+
# set.count_with_duplicates #=> 2
1401+
# set.count #=> 1
1402+
# set.cardinality #=> 2
13591403
#
1360-
# Related: #entries, #count_duplicates, #has_duplicates?
1404+
# Related: #count, #cardinality, #count_duplicates, #has_duplicates?,
1405+
# #entries
13611406
def count_with_duplicates
13621407
return count unless @string
13631408
each_entry_minmax.sum {|min, max|
@@ -1382,7 +1427,7 @@ def count_duplicates
13821427
#
13831428
# Always returns +false+ when #string is normalized.
13841429
#
1385-
# Related: #entries, #count_with_duplicates, #count_duplicates?
1430+
# Related: #entries, #count_with_duplicates, #count_duplicates
13861431
def has_duplicates?
13871432
return false unless @string
13881433
count_with_duplicates != count

test/net/imap/test_sequence_set.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ def obj.to_sequence_set; 192_168.001_255 end
441441
test "#[start, length]" do
442442
assert_equal SequenceSet[10..99], SequenceSet.full[9, 90]
443443
assert_equal 90, SequenceSet.full[9, 90].count
444+
assert_equal 90, SequenceSet.full[9, 90].cardinality
444445
assert_equal SequenceSet[1000..1099],
445446
SequenceSet[1..100, 1000..1111][100, 100]
446447
assert_equal SequenceSet[11, 21, 31, 41],
@@ -1065,6 +1066,7 @@ def test_inspect((expected, input, freeze))
10651066
to_s: "4294967000:*",
10661067
normalize: "4294967000:*",
10671068
count: 2**32 - 4_294_967_000,
1069+
cardinality: 2**32 - 4_294_967_000 + 1,
10681070
complement: "1:4294966999",
10691071
}, keep: true
10701072

@@ -1143,6 +1145,7 @@ def test_inspect((expected, input, freeze))
11431145
normalize: "2:*",
11441146
count: 2**32 - 2,
11451147
count_dups: 2**32 - 2,
1148+
cardinality: 2**32 - 1,
11461149
complement: "1",
11471150
}, keep: true
11481151

@@ -1329,6 +1332,15 @@ def assert_seqset_enum(expected, seqset, enum)
13291332
assert_equal data[:count], SequenceSet.new(data[:input]).count
13301333
end
13311334

1335+
test "#size" do |data|
1336+
assert_equal data[:count], SequenceSet.new(data[:input]).size
1337+
end
1338+
1339+
test "#cardinality" do |data|
1340+
expected = data[:cardinality] || data[:count]
1341+
assert_equal expected, SequenceSet.new(data[:input]).cardinality
1342+
end
1343+
13321344
test "#count_with_duplicates" do |data|
13331345
dups = data[:count_dups] || 0
13341346
count = data[:count] + dups

0 commit comments

Comments
 (0)