net-imap 0.5.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,21 +18,9 @@ module Net
18
18
  #
19
19
  # == Creating sequence sets
20
20
  #
21
- # SequenceSet.new with no arguments creates an empty sequence set. Note
22
- # that an empty sequence set is invalid in the \IMAP grammar.
23
- #
24
- # set = Net::IMAP::SequenceSet.new
25
- # set.empty? #=> true
26
- # set.valid? #=> false
27
- # set.valid_string #!> raises DataFormatError
28
- # set << 1..10
29
- # set.empty? #=> false
30
- # set.valid? #=> true
31
- # set.valid_string #=> "1:10"
32
- #
33
21
  # SequenceSet.new may receive a single optional argument: a non-zero 32 bit
34
22
  # unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
35
- # another sequence set, a Set (containing only numbers or <tt>*</tt>), or an
23
+ # another SequenceSet, a Set (containing only numbers or <tt>*</tt>), or an
36
24
  # Array containing any of these (array inputs may be nested).
37
25
  #
38
26
  # set = Net::IMAP::SequenceSet.new(1)
@@ -48,30 +36,118 @@ module Net
48
36
  # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
49
37
  # set.valid_string #=> "1:10,55,1024:2048"
50
38
  #
51
- # Use ::[] with one or more arguments to create a frozen SequenceSet. An
52
- # invalid (empty) set cannot be created with ::[].
39
+ # SequenceSet.new with no arguments creates an empty sequence set. Note
40
+ # that an empty sequence set is invalid in the \IMAP grammar.
41
+ #
42
+ # set = Net::IMAP::SequenceSet.new
43
+ # set.empty? #=> true
44
+ # set.valid? #=> false
45
+ # set.valid_string #!> raises DataFormatError
46
+ # set << 1..10
47
+ # set.empty? #=> false
48
+ # set.valid? #=> true
49
+ # set.valid_string #=> "1:10"
50
+ #
51
+ # Using SequenceSet.new with another SequenceSet input behaves the same as
52
+ # calling #dup on the other set. The input's #string will be preserved.
53
+ #
54
+ # input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
55
+ # copy = Net::IMAP::SequenceSet.new(input)
56
+ # input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
57
+ # copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
58
+ # copy2 = input.dup # same as calling new with a SequenceSet input
59
+ # copy == input #=> true, same set membership
60
+ # copy.eql? input #=> true, same string value
61
+ # copy.equal? input #=> false, different objects
62
+ #
63
+ # copy.normalize!
64
+ # copy.valid_string #=> "1:10,1024,2048"
65
+ # copy == input #=> true, same set membership
66
+ # copy.eql? input #=> false, different string value
67
+ #
68
+ # copy << 999
69
+ # copy.valid_string #=> "1:10,999,1024,2048"
70
+ # copy == input #=> false, different set membership
71
+ # copy.eql? input #=> false, different string value
72
+ #
73
+ # Use Net::IMAP::SequenceSet() to coerce a single (optional) input.
74
+ # A SequenceSet input is returned without duplication, even when frozen.
75
+ #
76
+ # set = Net::IMAP::SequenceSet()
77
+ # set.string #=> nil
78
+ # set.frozen? #=> false
79
+ #
80
+ # # String order is preserved
81
+ # set = Net::IMAP::SequenceSet("1,2,3:7,5,6:10,2048,1024")
82
+ # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
83
+ # set.frozen? #=> false
84
+ #
85
+ # # Other inputs are normalized
86
+ # set = Net::IMAP::SequenceSet([1, 2, [3..7, 5], 6..10, 2048, 1024])
87
+ # set.valid_string #=> "1:10,1024,2048"
88
+ # set.frozen? #=> false
89
+ #
90
+ # unfrozen = set
91
+ # frozen = set.dup.freeze
92
+ # unfrozen.equal? Net::IMAP::SequenceSet(unfrozen) #=> true
93
+ # frozen.equal? Net::IMAP::SequenceSet(frozen) #=> true
53
94
  #
95
+ # Use ::[] to coerce one or more arguments into a valid frozen SequenceSet.
96
+ # A valid frozen SequenceSet is returned directly, without allocating a new
97
+ # object. ::[] will not create an invalid (empty) set.
98
+ #
99
+ # Net::IMAP::SequenceSet[] #!> raises ArgumentError
100
+ # Net::IMAP::SequenceSet[nil] #!> raises DataFormatError
101
+ # Net::IMAP::SequenceSet[""] #!> raises DataFormatError
102
+ #
103
+ # # String order is preserved
54
104
  # set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"]
55
105
  # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
106
+ # set.frozen? #=> true
107
+ #
108
+ # # Other inputs are normalized
56
109
  # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
57
- # set.valid_string #=> "1:10,55,1024:2048"
110
+ # set.valid_string #=> "1:10,1024,2048"
111
+ # set.frozen? #=> true
112
+ #
113
+ # frozen = set
114
+ # unfrozen = set.dup
115
+ # frozen.equal? Net::IMAP::SequenceSet[frozen] #=> true
116
+ # unfrozen.equal? Net::IMAP::SequenceSet[unfrozen] #=> false
117
+ #
118
+ # Objects which respond to +to_sequence_set+ (such as SearchResult and
119
+ # ThreadMember) can be coerced to a SequenceSet with ::new, ::try_convert,
120
+ # ::[], or Net::IMAP::SequenceSet.
121
+ #
122
+ # search = imap.uid_search(["SUBJECT", "hello", "NOT", "SEEN"])
123
+ # seqset = Net::IMAP::SequenceSet(search) - already_fetched
124
+ # fetch = imap.uid_fetch(seqset, "FAST")
58
125
  #
59
126
  # == Ordered and Normalized sets
60
127
  #
61
128
  # Sometimes the order of the set's members is significant, such as with the
62
129
  # +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. So, when a
63
- # sequence set is created by the parser or with a single string value, that
64
- # #string representation is preserved.
130
+ # sequence set is created from a single string (such as by the parser), that
131
+ # #string representation is preserved. Assigning a string with #string= or
132
+ # #replace will also preserve that string. Use #each_entry, #entries, or
133
+ # #each_ordered_number to enumerate the entries in their #string order.
134
+ # Hash equality (using #eql?) is based on the string representation.
135
+ #
136
+ # Internally, SequenceSet uses a normalized uint32 set representation which
137
+ # sorts and de-duplicates all numbers and coalesces adjacent or overlapping
138
+ # entries. Many methods use this sorted set representation for <tt>O(lg
139
+ # n)</tt> searches. Use #each_element, #elements, #each_range, #ranges,
140
+ # #each_number, or #numbers to enumerate the set in sorted order. Basic
141
+ # object equality (using #==) is based on set membership, without regard to
142
+ # #entry order or #string normalization.
65
143
  #
66
- # Internally, SequenceSet stores a normalized representation which sorts all
67
- # entries, de-duplicates numbers, and coalesces adjacent or overlapping
68
- # ranges. Most methods use this normalized representation to achieve
69
- # <tt>O(lg n)</tt> porformance. Use #entries or #each_entry to enumerate
70
- # the set in its original order.
144
+ # Most modification methods reset #string to its #normalized form, so that
145
+ # #entries and #elements are identical. Use #append to preserve #entries
146
+ # order while modifying a set.
71
147
  #
72
- # Most modification methods convert #string to its normalized form. To
73
- # preserve #string order while modifying a set, use #append, #string=, or
74
- # #replace.
148
+ # Non-normalized sets store both representations of the set, which can more
149
+ # than double memory usage. Very large sequence sets should avoid
150
+ # denormalizing methods (such as #append) unless order is significant.
75
151
  #
76
152
  # == Using <tt>*</tt>
77
153
  #
@@ -107,12 +183,16 @@ module Net
107
183
  #
108
184
  # When a set includes <tt>*</tt>, some methods may have surprising behavior.
109
185
  #
110
- # For example, #complement treats <tt>*</tt> as its own number. This way,
111
- # the #intersection of a set and its #complement will always be empty.
112
- # This is not how an \IMAP server interprets the set: it will convert
113
- # <tt>*</tt> to either the number of messages in the mailbox or +UIDNEXT+,
114
- # as appropriate. And there _will_ be overlap between a set and its
115
- # complement after #limit is applied to each:
186
+ # For example, #complement treats <tt>*</tt> as its own member. This way,
187
+ # the #intersection of a set and its #complement will always be empty. And
188
+ # <tt>*</tt> is sorted as greater than any other number in the set. This is
189
+ # not how an \IMAP server interprets the set: it will convert <tt>*</tt> to
190
+ # the number of messages in the mailbox, the +UID+ of the last message in
191
+ # the mailbox, or +UIDNEXT+, as appropriate. Several methods have an
192
+ # argument for how <tt>*</tt> should be interpreted.
193
+ #
194
+ # But, for example, this means that there may be overlap between a set and
195
+ # its complement after #limit is applied to each:
116
196
  #
117
197
  # ~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)]
118
198
  # ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"]
@@ -123,7 +203,7 @@ module Net
123
203
  # (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4]
124
204
  #
125
205
  # When counting the number of numbers in a set, <tt>*</tt> will be counted
126
- # _except_ when UINT32_MAX is also in the set:
206
+ # as if it were equal to UINT32_MAX:
127
207
  # UINT32_MAX = 2**32 - 1
128
208
  # Net::IMAP::SequenceSet["*"].count => 1
129
209
  # Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX
@@ -132,6 +212,12 @@ module Net
132
212
  # Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1
133
213
  # Net::IMAP::SequenceSet[UINT32_MAX..].count => 1
134
214
  #
215
+ # Use #cardinality to count the set members wxth <tt>*</tt> counted as a
216
+ # distinct member:
217
+ # Net::IMAP::SequenceSet[1..].cardinality #=> UINT32_MAX + 1
218
+ # Net::IMAP::SequenceSet[UINT32_MAX, :*].cardinality #=> 2
219
+ # Net::IMAP::SequenceSet[UINT32_MAX..].cardinality #=> 2
220
+ #
135
221
  # == What's here?
136
222
  #
137
223
  # SequenceSet provides methods for:
@@ -149,6 +235,7 @@ module Net
149
235
  # * ::new: Creates a new mutable sequence set, which may be empty (invalid).
150
236
  # * ::try_convert: Calls +to_sequence_set+ on an object and verifies that
151
237
  # the result is a SequenceSet.
238
+ # * Net::IMAP::SequenceSet(): Coerce an input using ::try_convert or ::new.
152
239
  # * ::empty: Returns a frozen empty (invalid) SequenceSet.
153
240
  # * ::full: Returns a frozen SequenceSet containing every possible number.
154
241
  #
@@ -174,14 +261,13 @@ module Net
174
261
  #
175
262
  # <i>Set membership:</i>
176
263
  # - #include? (aliased as #member?):
177
- # Returns whether a given object (nz-number, range, or <tt>*</tt>) is
178
- # contained by the set.
264
+ # Returns whether a given element is contained by the set.
179
265
  # - #include_star?: Returns whether the set contains <tt>*</tt>.
180
266
  #
181
267
  # <i>Minimum and maximum value elements:</i>
182
- # - #min: Returns the minimum number in the set.
183
- # - #max: Returns the maximum number in the set.
184
- # - #minmax: Returns the minimum and maximum numbers in the set.
268
+ # - #min: Returns one or more of the lowest numbers in the set.
269
+ # - #max: Returns one or more of the highest numbers in the set.
270
+ # - #minmax: Returns the lowest and highest numbers in the set.
185
271
  #
186
272
  # <i>Accessing value by offset in sorted set:</i>
187
273
  # - #[] (aliased as #slice): Returns the number or consecutive subset at a
@@ -195,8 +281,10 @@ module Net
195
281
  # occurrence in entries.
196
282
  #
197
283
  # <i>Set cardinality:</i>
198
- # - #count (aliased as #size): Returns the count of numbers in the set.
199
- # Duplicated numbers are not counted.
284
+ # - #cardinality: Returns the number of distinct members in the set.
285
+ # <tt>*</tt> is counted as its own member, distinct from UINT32_MAX.
286
+ # - #count: Returns the count of distinct numbers in the set.
287
+ # <tt>*</tt> is counted as equal to UINT32_MAX.
200
288
  # - #empty?: Returns whether the set has no members. \IMAP syntax does not
201
289
  # allow empty sequence sets.
202
290
  # - #valid?: Returns whether the set has any members.
@@ -204,12 +292,18 @@ module Net
204
292
  # <tt>*</tt>.
205
293
  #
206
294
  # <i>Denormalized properties:</i>
295
+ # - #normalized?: Returns whether #entries are sorted, deduplicated, and
296
+ # coalesced, and all #string entries are in normalized form.
207
297
  # - #has_duplicates?: Returns whether the ordered entries repeat any
208
298
  # numbers.
209
- # - #count_duplicates: Returns the count of repeated numbers in the ordered
210
- # entries.
299
+ # - #size: Returns the total size of all #entries, including repeated
300
+ # numbers. <tt>*</tt> is counted as its own member, distinct from
301
+ # UINT32_MAX.
211
302
  # - #count_with_duplicates: Returns the count of numbers in the ordered
212
- # entries, including any repeated numbers.
303
+ # #entries, including repeated numbers. <tt>*</tt> is counted as
304
+ # equal to UINT32_MAX.
305
+ # - #count_duplicates: Returns the count of repeated numbers in the ordered
306
+ # #entries. <tt>*</tt> is counted as equal to UINT32_MAX.
213
307
  #
214
308
  # === Methods for Iterating
215
309
  #
@@ -239,53 +333,64 @@ module Net
239
333
  # These methods do not modify +self+.
240
334
  #
241
335
  # - #| (aliased as #union and #+): Returns a new set combining all members
242
- # from +self+ with all members from the other object.
336
+ # from +self+ with all members from the other set.
243
337
  # - #& (aliased as #intersection): Returns a new set containing all members
244
- # common to +self+ and the other object.
338
+ # common to +self+ and the other set.
245
339
  # - #- (aliased as #difference): Returns a copy of +self+ with all members
246
- # in the other object removed.
340
+ # in the other set removed.
247
341
  # - #^ (aliased as #xor): Returns a new set containing all members from
248
- # +self+ and the other object except those common to both.
342
+ # +self+ and the other set except those common to both.
249
343
  # - #~ (aliased as #complement): Returns a new set containing all members
250
344
  # that are not in +self+
345
+ # - #above: Return a copy of +self+ which only contains numbers above a
346
+ # given number.
347
+ # - #below: Return a copy of +self+ which only contains numbers below a
348
+ # given value.
251
349
  # - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
252
350
  # given maximum value and removed all members over that maximum.
253
351
  #
254
352
  # === Methods for Assigning
255
- # These methods add or replace elements in +self+.
353
+ # These methods add or replace numbers in +self+.
256
354
  #
257
355
  # <i>Normalized (sorted and coalesced):</i>
258
356
  #
259
357
  # These methods always update #string to be fully sorted and coalesced.
260
358
  #
261
- # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
262
- # - #add?: If the given object is not an element in the set, adds it and
359
+ # - #add (aliased as #<<): Adds a given element to the set; returns +self+.
360
+ # - #add?: If the given element is not fully included the set, adds it and
263
361
  # returns +self+; otherwise, returns +nil+.
264
- # - #merge: Merges multiple elements into the set; returns +self+.
265
- # - #complement!: Replaces the contents of the set with its own #complement.
362
+ # - #merge: In-place set #union. Adds all members of the given sets into
363
+ # this set; returns +self+.
364
+ # - #complement!: In-place set #complement. Replaces the contents of this
365
+ # set with its own #complement; returns +self+.
366
+ # - #xor!: In-place +XOR+ operation. Adds numbers that are unique to the
367
+ # other set and removes numbers that are common to both; returns +self+.
266
368
  #
267
369
  # <i>Order preserving:</i>
268
370
  #
269
371
  # These methods _may_ cause #string to not be sorted or coalesced.
270
372
  #
271
- # - #append: Adds a given object to the set, appending it to the existing
373
+ # - #append: Adds the given entry to the set, appending it to the existing
272
374
  # string, and returns +self+.
273
375
  # - #string=: Assigns a new #string value and replaces #elements to match.
274
376
  # - #replace: Replaces the contents of the set with the contents
275
377
  # of a given object.
276
378
  #
277
379
  # === Methods for Deleting
278
- # These methods remove elements from +self+, and update #string to be fully
380
+ # These methods remove numbers from +self+, and update #string to be fully
279
381
  # sorted and coalesced.
280
382
  #
281
383
  # - #clear: Removes all elements in the set; returns +self+.
282
- # - #delete: Removes a given object from the set; returns +self+.
283
- # - #delete?: If the given object is an element in the set, removes it and
384
+ # - #delete: Removes a given element from the set; returns +self+.
385
+ # - #delete?: If the given element is included in the set, removes it and
284
386
  # returns it; otherwise, returns +nil+.
285
387
  # - #delete_at: Removes the number at a given offset.
388
+ # - #intersect!: In-place set #intersection. Removes numbers that are not
389
+ # in the given set; returns +self+.
286
390
  # - #slice!: Removes the number or consecutive numbers at a given offset or
287
391
  # range of offsets.
288
- # - #subtract: Removes each given object from the set; returns +self+.
392
+ # - #subtract: In-place set #difference. Removes all members of the given
393
+ # sets from this set; returns +self+.
289
394
  # - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
290
395
  # members over that maximum; returns +self+.
291
396
  #
@@ -315,23 +420,42 @@ module Net
315
420
  STARS = [:*, ?*, -1].freeze
316
421
  private_constant :STARS
317
422
 
423
+ INSPECT_MAX_LEN = 512
424
+ INSPECT_TRUNCATE_LEN = 16
425
+ private_constant :INSPECT_MAX_LEN, :INSPECT_TRUNCATE_LEN
426
+
427
+ # /(,\d+){100}\z/ is shockingly slow on huge strings.
428
+ # /(,\d{0,10}){100}\z/ is ok, but ironically, Regexp.linear_time? is false.
429
+ #
430
+ # This unrolls all nested quantifiers. It's much harder to read, but it's
431
+ # also the fastest out of all the versions I tested.
432
+ nz_uint32 = /[1-9](?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d)?)?)?)?)?)?)?)?)?/
433
+ num_or_star = /#{nz_uint32}|\*/
434
+ entry = /#{num_or_star}(?::#{num_or_star})?/
435
+ entries = ([entry] * INSPECT_TRUNCATE_LEN).join(",")
436
+ INSPECT_ABRIDGED_HEAD_RE = /\A#{entries},/
437
+ INSPECT_ABRIDGED_TAIL_RE = /,#{entries}\z/
438
+ private_constant :INSPECT_ABRIDGED_HEAD_RE, :INSPECT_ABRIDGED_TAIL_RE
439
+
318
440
  class << self
319
441
 
320
442
  # :call-seq:
321
- # SequenceSet[*values] -> valid frozen sequence set
443
+ # SequenceSet[*inputs] -> valid frozen sequence set
444
+ #
445
+ # Returns a frozen SequenceSet, constructed from +inputs+.
322
446
  #
323
- # Returns a frozen SequenceSet, constructed from +values+.
447
+ # When only a single valid frozen SequenceSet is given, that same set is
448
+ # returned.
324
449
  #
325
450
  # An empty SequenceSet is invalid and will raise a DataFormatError.
326
451
  #
327
452
  # Use ::new to create a mutable or empty SequenceSet.
453
+ #
454
+ # Related: ::new, Net::IMAP::SequenceSet(), ::try_convert
328
455
  def [](first, *rest)
329
456
  if rest.empty?
330
- if first.is_a?(SequenceSet) && first.frozen? && first.valid?
331
- first
332
- else
333
- new(first).validate.freeze
334
- end
457
+ set = try_convert(first)&.validate
458
+ set&.frozen? ? set : (set&.dup || new(first).validate).freeze
335
459
  else
336
460
  new(first).merge(*rest).validate.freeze
337
461
  end
@@ -344,12 +468,14 @@ module Net
344
468
  # +to_sequence_set+, calls +obj.to_sequence_set+ and returns the result.
345
469
  # Otherwise returns +nil+.
346
470
  #
347
- # If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is
348
- # raised.
471
+ # If +obj.to_sequence_set+ doesn't return a SequenceSet or +nil+, an
472
+ # exception is raised.
473
+ #
474
+ # Related: Net::IMAP::SequenceSet(), ::new, ::[]
349
475
  def try_convert(obj)
350
476
  return obj if obj.is_a?(SequenceSet)
351
477
  return nil unless obj.respond_to?(:to_sequence_set)
352
- obj = obj.to_sequence_set
478
+ return nil unless obj = obj.to_sequence_set
353
479
  return obj if obj.is_a?(SequenceSet)
354
480
  raise DataFormatError, "invalid object returned from to_sequence_set"
355
481
  end
@@ -364,23 +490,96 @@ module Net
364
490
  end
365
491
 
366
492
  # Create a new SequenceSet object from +input+, which may be another
367
- # SequenceSet, an IMAP formatted +sequence-set+ string, a number, a
368
- # range, <tt>:*</tt>, or an enumerable of these.
369
- #
370
- # Use ::[] to create a frozen (non-empty) SequenceSet.
371
- def initialize(input = nil) input ? replace(input) : clear end
493
+ # SequenceSet, an IMAP formatted +sequence-set+ string, a non-zero 32 bit
494
+ # unsigned integer, a range, <tt>:*</tt>, a Set of numbers or <tt>*</tt>,
495
+ # an object that responds to +to_sequence_set+ (such as SearchResult) or
496
+ # an Array of these (array inputs may be nested).
497
+ #
498
+ # set = Net::IMAP::SequenceSet.new(1)
499
+ # set.valid_string #=> "1"
500
+ # set = Net::IMAP::SequenceSet.new(1..100)
501
+ # set.valid_string #=> "1:100"
502
+ # set = Net::IMAP::SequenceSet.new(1...100)
503
+ # set.valid_string #=> "1:99"
504
+ # set = Net::IMAP::SequenceSet.new([1, 2, 5..])
505
+ # set.valid_string #=> "1:2,5:*"
506
+ # set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
507
+ # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
508
+ # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
509
+ # set.valid_string #=> "1:10,1024,2048"
510
+ #
511
+ # With no arguments (or +nil+) creates an empty sequence set. Note that
512
+ # an empty sequence set is invalid in the \IMAP grammar.
513
+ #
514
+ # set = Net::IMAP::SequenceSet.new
515
+ # set.empty? #=> true
516
+ # set.valid? #=> false
517
+ # set.valid_string #!> raises DataFormatError
518
+ # set << 1..10
519
+ # set.empty? #=> false
520
+ # set.valid? #=> true
521
+ # set.valid_string #=> "1:10"
522
+ #
523
+ # When +input+ is a SequenceSet, ::new behaves the same as calling #dup on
524
+ # that other set. The input's #string will be preserved.
525
+ #
526
+ # input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
527
+ # copy = Net::IMAP::SequenceSet.new(input)
528
+ # input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
529
+ # copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
530
+ # copy2 = input.dup # same as calling new with a SequenceSet input
531
+ # copy == input #=> true, same set membership
532
+ # copy.eql? input #=> true, same string value
533
+ # copy.equal? input #=> false, different objects
534
+ #
535
+ # copy.normalize!
536
+ # copy.valid_string #=> "1:10,1024,2048"
537
+ # copy == input #=> true, same set membership
538
+ # copy.eql? input #=> false, different string value
539
+ #
540
+ # copy << 999
541
+ # copy.valid_string #=> "1:10,999,1024,2048"
542
+ # copy == input #=> false, different set membership
543
+ # copy.eql? input #=> false, different string value
544
+ #
545
+ # === Alternative set creation methods
546
+ #
547
+ # * ::[] returns a frozen validated (non-empty) SequenceSet, without
548
+ # allocating a new object when the input is already a valid frozen
549
+ # SequenceSet.
550
+ # * Net::IMAP::SequenceSet() coerces an input to SequenceSet, without
551
+ # allocating a new object when the input is already a SequenceSet.
552
+ # * ::try_convert calls +to_sequence_set+ on inputs that support it and
553
+ # returns +nil+ for inputs that don't.
554
+ # * ::empty and ::full both return frozen singleton sets which can be
555
+ # combined with set operations (#|, #&, #^, #-, etc) to make new sets.
556
+ #
557
+ # See SequenceSet@Creating+sequence+sets.
558
+ def initialize(input = nil)
559
+ @set_data = new_set_data
560
+ @string = nil
561
+ replace(input) unless input.nil?
562
+ end
372
563
 
373
564
  # Removes all elements and returns self.
374
- def clear; @tuples, @string = [], nil; self end
565
+ def clear
566
+ modifying! # redundant check (normalizes the error message for JRuby)
567
+ set_data.clear
568
+ @string = nil
569
+ self
570
+ end
375
571
 
376
572
  # Replace the contents of the set with the contents of +other+ and returns
377
573
  # +self+.
378
574
  #
379
- # +other+ may be another SequenceSet, or it may be an IMAP +sequence-set+
380
- # string, a number, a range, <tt>*</tt>, or an enumerable of these.
575
+ # +other+ may be another SequenceSet or any other object that would be
576
+ # accepted by ::new.
381
577
  def replace(other)
382
578
  case other
383
- when SequenceSet then initialize_dup(other)
579
+ when SequenceSet then
580
+ modifying! # short circuit before doing any work
581
+ @set_data = other.dup_set_data
582
+ @string = other.instance_variable_get(:@string)
384
583
  when String then self.string = other
385
584
  else clear; merge other
386
585
  end
@@ -409,43 +608,51 @@ module Net
409
608
  # If the set was created from a single string, it is not normalized. If
410
609
  # the set is updated the string will be normalized.
411
610
  #
412
- # Related: #valid_string, #normalized_string, #to_s
413
- def string; @string ||= normalized_string if valid? end
611
+ # Related: #valid_string, #normalized_string, #to_s, #inspect
612
+ def string; @string || normalized_string if valid? end
414
613
 
415
614
  # Returns an array with #normalized_string when valid and an empty array
416
615
  # otherwise.
417
616
  def deconstruct; valid? ? [normalized_string] : [] end
418
617
 
419
- # Assigns a new string to #string and resets #elements to match. It
420
- # cannot be set to an empty string—assign +nil+ or use #clear instead.
421
- # The string is validated but not normalized.
618
+ # Assigns a new string to #string and resets #elements to match.
619
+ # Assigning +nil+ or an empty string are equivalent to calling #clear.
620
+ #
621
+ # Non-empty strings are validated but not normalized.
422
622
  #
423
- # Use #add or #merge to add a string to an existing set.
623
+ # Use #add, #merge, or #append to add a string to an existing set.
424
624
  #
425
625
  # Related: #replace, #clear
426
- def string=(str)
427
- if str.nil?
626
+ def string=(input)
627
+ if input.nil?
428
628
  clear
629
+ elsif (str = String.try_convert(input))
630
+ modifying! # short-circuit before parsing the string
631
+ entries = each_parsed_entry(str).to_a
632
+ clear
633
+ if normalized_entries?(entries)
634
+ replace_minmaxes entries.map!(&:minmax)
635
+ else
636
+ add_minmaxes entries.map!(&:minmax)
637
+ @string = -str
638
+ end
429
639
  else
430
- str = String.try_convert(str) or raise ArgumentError, "not a string"
431
- tuples = str_to_tuples str
432
- @tuples, @string = [], -str
433
- tuples_add tuples
640
+ raise ArgumentError, "expected a string or nil, got #{input.class}"
434
641
  end
642
+ input
435
643
  end
436
644
 
437
645
  # Returns the \IMAP +sequence-set+ string representation, or an empty
438
646
  # string when the set is empty. Note that an empty set is invalid in the
439
647
  # \IMAP syntax.
440
648
  #
441
- # Related: #valid_string, #normalized_string, #to_s
649
+ # Related: #string, #valid_string, #normalized_string, #inspect
442
650
  def to_s; string || "" end
443
651
 
444
652
  # Freezes and returns the set. A frozen SequenceSet is Ractor-safe.
445
653
  def freeze
446
654
  return self if frozen?
447
- string
448
- @tuples.each(&:freeze).freeze
655
+ freeze_set_data
449
656
  super
450
657
  end
451
658
 
@@ -467,7 +674,7 @@ module Net
467
674
  # Related: #eql?, #normalize
468
675
  def ==(other)
469
676
  self.class == other.class &&
470
- (to_s == other.to_s || tuples == other.tuples)
677
+ (to_s == other.to_s || set_data == other.set_data)
471
678
  end
472
679
 
473
680
  # :call-seq: eql?(other) -> true or false
@@ -491,8 +698,9 @@ module Net
491
698
 
492
699
  # :call-seq: self === other -> true | false | nil
493
700
  #
494
- # Returns whether +other+ is contained within the set. Returns +nil+ if a
495
- # StandardError is raised while converting +other+ to a comparable type.
701
+ # Returns whether +other+ is contained within the set. +other+ may be any
702
+ # object that would be accepted by ::new. Returns +nil+ if StandardError
703
+ # is raised while converting +other+ to a comparable type.
496
704
  #
497
705
  # Related: #cover?, #include?, #include_star?
498
706
  def ===(other)
@@ -506,12 +714,12 @@ module Net
506
714
  # Returns whether +other+ is contained within the set. +other+ may be any
507
715
  # object that would be accepted by ::new.
508
716
  #
509
- # Related: #===, #include?, #include_star?
510
- def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
717
+ # Related: #===, #include?, #include_star?, #intersect?
718
+ def cover?(other) import_runs(other).none? { !include_run?(_1) } end
511
719
 
512
720
  # Returns +true+ when a given number or range is in +self+, and +false+
513
- # otherwise. Returns +false+ unless +number+ is an Integer, Range, or
514
- # <tt>*</tt>.
721
+ # otherwise. Returns +nil+ when +number+ isn't a valid SequenceSet
722
+ # element (Integer, Range, <tt>*</tt>, +sequence-set+ string).
515
723
  #
516
724
  # set = Net::IMAP::SequenceSet["5:10,100,111:115"]
517
725
  # set.include? 1 #=> false
@@ -519,8 +727,8 @@ module Net
519
727
  # set.include? 11..20 #=> false
520
728
  # set.include? 100 #=> true
521
729
  # set.include? 6 #=> true, covered by "5:10"
522
- # set.include? 4..9 #=> true, covered by "5:10"
523
- # set.include? "4:9" #=> true, strings are parsed
730
+ # set.include? 6..9 #=> true, covered by "5:10"
731
+ # set.include? "6:9" #=> true, strings are parsed
524
732
  # set.include? 4..9 #=> false, intersection is not sufficient
525
733
  # set.include? "*" #=> false, use #limit to re-interpret "*"
526
734
  # set.include? -1 #=> false, -1 is interpreted as "*"
@@ -529,16 +737,19 @@ module Net
529
737
  # set.include? :* #=> true
530
738
  # set.include? "*" #=> true
531
739
  # set.include? -1 #=> true
532
- # set.include? 200.. #=> true
533
- # set.include? 100.. #=> false
740
+ # set.include?(200..) #=> true
741
+ # set.include?(100..) #=> false
534
742
  #
535
- # Related: #include_star?, #cover?, #===
536
- def include?(element) include_tuple? input_to_tuple element end
743
+ # Related: #include_star?, #cover?, #===, #intersect?
744
+ def include?(element)
745
+ run = import_run element rescue nil
746
+ !!include_run?(run) if run
747
+ end
537
748
 
538
749
  alias member? include?
539
750
 
540
751
  # Returns +true+ when the set contains <tt>*</tt>.
541
- def include_star?; @tuples.last&.last == STAR_INT end
752
+ def include_star?; max_num == STAR_INT end
542
753
 
543
754
  # Returns +true+ if the set and a given object have any common elements,
544
755
  # +false+ otherwise.
@@ -546,9 +757,9 @@ module Net
546
757
  # Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true
547
758
  # Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false
548
759
  #
549
- # Related: #intersection, #disjoint?
760
+ # Related: #intersection, #disjoint?, #cover?, #include?
550
761
  def intersect?(other)
551
- valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
762
+ valid? && import_runs(other).any? { intersect_run? _1 }
552
763
  end
553
764
  alias overlap? intersect?
554
765
 
@@ -560,39 +771,66 @@ module Net
560
771
  #
561
772
  # Related: #intersection, #intersect?
562
773
  def disjoint?(other)
563
- empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
774
+ empty? || import_runs(other).none? { intersect_run? _1 }
564
775
  end
565
776
 
566
- # :call-seq: max(star: :*) => integer or star or nil
777
+ # :call-seq:
778
+ # max(star: :*) => integer or star or nil
779
+ # max(count) => SequenceSet
567
780
  #
568
781
  # Returns the maximum value in +self+, +star+ when the set includes
569
782
  # <tt>*</tt>, or +nil+ when the set is empty.
570
- def max(star: :*)
571
- (val = @tuples.last&.last) && val == STAR_INT ? star : val
783
+ #
784
+ # When +count+ is given, a new SequenceSet is returned, containing only
785
+ # the last +count+ numbers. An empty SequenceSet is returned when +self+
786
+ # is empty. (+star+ is ignored when +count+ is given.)
787
+ #
788
+ # Related: #min, #minmax, #slice
789
+ def max(count = nil, star: :*)
790
+ if count
791
+ slice(-[count, size].min..) || remain_frozen_empty
792
+ elsif (val = max_num)
793
+ val == STAR_INT ? star : val
794
+ end
572
795
  end
573
796
 
574
- # :call-seq: min(star: :*) => integer or star or nil
797
+ # :call-seq:
798
+ # min(star: :*) => integer or star or nil
799
+ # min(count) => SequenceSet
575
800
  #
576
801
  # Returns the minimum value in +self+, +star+ when the only value in the
577
802
  # set is <tt>*</tt>, or +nil+ when the set is empty.
578
- def min(star: :*)
579
- (val = @tuples.first&.first) && val == STAR_INT ? star : val
803
+ #
804
+ # When +count+ is given, a new SequenceSet is returned, containing only
805
+ # the first +count+ numbers. An empty SequenceSet is returned when +self+
806
+ # is empty. (+star+ is ignored when +count+ is given.)
807
+ #
808
+ # Related: #max, #minmax, #slice
809
+ def min(count = nil, star: :*)
810
+ if count
811
+ slice(0...count) || remain_frozen_empty
812
+ elsif (val = min_num)
813
+ val != STAR_INT ? val : star
814
+ end
580
815
  end
581
816
 
582
- # :call-seq: minmax(star: :*) => nil or [integer, integer or star]
817
+ # :call-seq: minmax(star: :*) => [min, max] or nil
583
818
  #
584
819
  # Returns a 2-element array containing the minimum and maximum numbers in
585
- # +self+, or +nil+ when the set is empty.
820
+ # +self+, or +nil+ when the set is empty. +star+ is handled the same way
821
+ # as by #min and #max.
822
+ #
823
+ # Related: #min, #max
586
824
  def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
587
825
 
588
826
  # Returns false when the set is empty.
589
827
  def valid?; !empty? end
590
828
 
591
829
  # Returns true if the set contains no elements
592
- def empty?; @tuples.empty? end
830
+ def empty?; runs.empty? end
593
831
 
594
832
  # Returns true if the set contains every possible element.
595
- def full?; @tuples == [[1, STAR_INT]] end
833
+ def full?; set_data == FULL_SET_DATA end
596
834
 
597
835
  # :call-seq:
598
836
  # self + other -> sequence set
@@ -602,14 +840,19 @@ module Net
602
840
  # Returns a new sequence set that has every number in the +other+ object
603
841
  # added.
604
842
  #
605
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
606
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
607
- # another sequence set, or an enumerable containing any of these.
843
+ # +other+ may be any object that would be accepted by ::new.
608
844
  #
609
845
  # Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
610
846
  # #=> Net::IMAP::SequenceSet["1:6,99"]
611
847
  #
612
- # Related: #add, #merge
848
+ # Related: #add, #merge, #&, #-, #^, #~
849
+ #
850
+ # ==== Set identities
851
+ #
852
+ # <tt>lhs | rhs</tt> is equivalent to:
853
+ # * <tt>rhs | lhs</tt> (commutative)
854
+ # * <tt>~(~lhs & ~rhs)</tt> (De Morgan's Law)
855
+ # * <tt>(lhs & rhs) ^ (lhs ^ rhs)</tt>
613
856
  def |(other) remain_frozen dup.merge other end
614
857
  alias :+ :|
615
858
  alias union :|
@@ -621,14 +864,22 @@ module Net
621
864
  # Returns a new sequence set built by duplicating this set and removing
622
865
  # every number that appears in +other+.
623
866
  #
624
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
625
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
626
- # another sequence set, or an enumerable containing any of these.
867
+ # +other+ may be any object that would be accepted by ::new.
627
868
  #
628
869
  # Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
629
870
  # #=> Net::IMAP::SequenceSet["1,3,5"]
630
871
  #
631
- # Related: #subtract
872
+ # Related: #subtract, #|, #&, #^, #~
873
+ #
874
+ # ==== Set identities
875
+ #
876
+ # <tt>lhs - rhs</tt> is equivalent to:
877
+ # * <tt>~rhs - ~lhs</tt>
878
+ # * <tt>lhs & ~rhs</tt>
879
+ # * <tt>~(~lhs | rhs)</tt>
880
+ # * <tt>lhs & (lhs ^ rhs)</tt>
881
+ # * <tt>lhs ^ (lhs & rhs)</tt>
882
+ # * <tt>rhs ^ (lhs | rhs)</tt>
632
883
  def -(other) remain_frozen dup.subtract other end
633
884
  alias difference :-
634
885
 
@@ -639,17 +890,23 @@ module Net
639
890
  # Returns a new sequence set containing only the numbers common to this
640
891
  # set and +other+.
641
892
  #
642
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
643
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
644
- # another sequence set, or an enumerable containing any of these.
893
+ # +other+ may be any object that would be accepted by ::new.
645
894
  #
646
895
  # Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
647
896
  # #=> Net::IMAP::SequenceSet["2,4"]
648
897
  #
649
- # <tt>(seqset & other)</tt> is equivalent to <tt>(seqset - ~other)</tt>.
650
- def &(other)
651
- remain_frozen dup.subtract SequenceSet.new(other).complement!
652
- end
898
+ # Related: #intersect?, #|, #-, #^, #~
899
+ #
900
+ # ==== Set identities
901
+ #
902
+ # <tt>lhs & rhs</tt> is equivalent to:
903
+ # * <tt>rhs & lhs</tt> (commutative)
904
+ # * <tt>~(~lhs | ~rhs)</tt> (De Morgan's Law)
905
+ # * <tt>lhs - ~rhs</tt>
906
+ # * <tt>lhs - (lhs - rhs)</tt>
907
+ # * <tt>lhs - (lhs ^ rhs)</tt>
908
+ # * <tt>lhs ^ (lhs - rhs)</tt>
909
+ def &(other) remain_frozen dup.intersect! other end
653
910
  alias intersection :&
654
911
 
655
912
  # :call-seq:
@@ -659,16 +916,22 @@ module Net
659
916
  # Returns a new sequence set containing numbers that are exclusive between
660
917
  # this set and +other+.
661
918
  #
662
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
663
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
664
- # another sequence set, or an enumerable containing any of these.
919
+ # +other+ may be any object that would be accepted by ::new.
665
920
  #
666
921
  # Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
667
922
  # #=> Net::IMAP::SequenceSet["1,3,5:6"]
668
923
  #
669
- # <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
670
- # (seqset & other))</tt>.
671
- def ^(other) remain_frozen (self | other).subtract(self & other) end
924
+ # Related: #|, #&, #-, #~
925
+ #
926
+ # ==== Set identities
927
+ #
928
+ # <tt>lhs ^ rhs</tt> is equivalent to:
929
+ # * <tt>rhs ^ lhs</tt> (commutative)
930
+ # * <tt>~lhs ^ ~rhs</tt>
931
+ # * <tt>(lhs | rhs) - (lhs & rhs)</tt>
932
+ # * <tt>(lhs - rhs) | (rhs - lhs)</tt>
933
+ # * <tt>(lhs ^ other) ^ (other ^ rhs)</tt>
934
+ def ^(other) remain_frozen dup.xor! other end
672
935
  alias xor :^
673
936
 
674
937
  # :call-seq:
@@ -685,21 +948,30 @@ module Net
685
948
  # ~Net::IMAP::SequenceSet["6:99,223:*"]
686
949
  # #=> Net::IMAP::SequenceSet["1:5,100:222"]
687
950
  #
688
- # Related: #complement!
951
+ # Related: #complement!, #|, #&, #-, #^
952
+ #
953
+ # ==== Set identities
954
+ #
955
+ # <tt>~set</tt> is equivalent to:
956
+ # * <tt>full - set</tt>, where "full" is Net::IMAP::SequenceSet.full
689
957
  def ~; remain_frozen dup.complement! end
690
958
  alias complement :~
691
959
 
692
960
  # :call-seq:
693
- # add(object) -> self
961
+ # add(element) -> self
694
962
  # self << other -> self
695
963
  #
696
964
  # Adds a range or number to the set and returns +self+.
697
965
  #
698
966
  # #string will be regenerated. Use #merge to add many elements at once.
699
967
  #
700
- # Related: #add?, #merge, #union
701
- def add(object)
702
- tuple_add input_to_tuple object
968
+ # Use #append to append new elements to #string. See
969
+ # SequenceSet@Ordered+and+Normalized+sets.
970
+ #
971
+ # Related: #add?, #merge, #union, #append
972
+ def add(element)
973
+ modifying! # short-circuit before import_run
974
+ add_run import_run element
703
975
  normalize!
704
976
  end
705
977
  alias << add
@@ -708,29 +980,73 @@ module Net
708
980
  #
709
981
  # Unlike #add, #merge, or #union, the new value is appended to #string.
710
982
  # This may result in a #string which has duplicates or is out-of-order.
711
- def append(object)
712
- modifying!
713
- tuple = input_to_tuple object
714
- entry = tuple_to_str tuple
715
- string unless empty? # write @string before tuple_add
716
- tuple_add tuple
717
- @string = -(@string ? "#{@string},#{entry}" : entry)
983
+ #
984
+ # set = Net::IMAP::SequenceSet.new
985
+ # set.append(1..2) # => Net::IMAP::SequenceSet("1:2")
986
+ # set.append(5) # => Net::IMAP::SequenceSet("1:2,5")
987
+ # set.append(4) # => Net::IMAP::SequenceSet("1:2,5,4")
988
+ # set.append(3) # => Net::IMAP::SequenceSet("1:2,5,4,3")
989
+ # set.append(2) # => Net::IMAP::SequenceSet("1:2,5,4,3,2")
990
+ #
991
+ # If +entry+ is a string, it will be converted into normal form.
992
+ #
993
+ # set = Net::IMAP::SequenceSet("4:5,1:2")
994
+ # set.append("6:6") # => Net::IMAP::SequenceSet("4:5,1:2,6")
995
+ # set.append("9:8") # => Net::IMAP::SequenceSet("4:5,1:2,6,8:9")
996
+ #
997
+ # If +entry+ adjacently follows the last entry, they will coalesced:
998
+ # set = Net::IMAP::SequenceSet.new("2,1,9:10")
999
+ # set.append(11..12) # => Net::IMAP::SequenceSet("2,1,9:12")
1000
+ #
1001
+ # Non-normalized sets store the string <em>in addition to</em> an internal
1002
+ # normalized uint32 set representation. This can more than double memory
1003
+ # usage, so large sets should avoid using #append unless preserving order
1004
+ # is required. See SequenceSet@Ordered+and+Normalized+sets.
1005
+ #
1006
+ # Related: #add, #merge, #union
1007
+ def append(entry)
1008
+ modifying! # short-circuit before import_minmax
1009
+ minmax = import_minmax entry
1010
+ adj = minmax.first - 1
1011
+ if @string.nil? && (runs.empty? || max_num <= adj)
1012
+ # append to elements or coalesce with last element
1013
+ add_minmax minmax
1014
+ return self
1015
+ elsif @string.nil?
1016
+ # generate string for out-of-order append
1017
+ head, comma = normalized_string, ","
1018
+ else
1019
+ # @string already exists... maybe coalesce with last entry
1020
+ head, comma, last_entry = @string.rpartition(",")
1021
+ last_min, last_max = import_minmax last_entry
1022
+ if last_max == adj
1023
+ # coalesce with last entry
1024
+ minmax[0] = last_min
1025
+ else
1026
+ # append to existing string
1027
+ head, comma = @string, ","
1028
+ end
1029
+ end
1030
+ entry = export_minmax minmax
1031
+ add_minmax minmax
1032
+ @string = -"#{head}#{comma}#{entry}"
718
1033
  self
719
1034
  end
720
1035
 
721
- # :call-seq: add?(object) -> self or nil
1036
+ # :call-seq: add?(element) -> self or nil
722
1037
  #
723
1038
  # Adds a range or number to the set and returns +self+. Returns +nil+
724
- # when the object is already included in the set.
1039
+ # when the element is already included in the set.
725
1040
  #
726
1041
  # #string will be regenerated. Use #merge to add many elements at once.
727
1042
  #
728
1043
  # Related: #add, #merge, #union, #include?
729
- def add?(object)
730
- add object unless include? object
1044
+ def add?(element)
1045
+ modifying! # short-circuit before include?
1046
+ add element unless include? element
731
1047
  end
732
1048
 
733
- # :call-seq: delete(object) -> self
1049
+ # :call-seq: delete(element) -> self
734
1050
  #
735
1051
  # Deletes the given range or number from the set and returns +self+.
736
1052
  #
@@ -738,8 +1054,9 @@ module Net
738
1054
  # many elements at once.
739
1055
  #
740
1056
  # Related: #delete?, #delete_at, #subtract, #difference
741
- def delete(object)
742
- tuple_subtract input_to_tuple object
1057
+ def delete(element)
1058
+ modifying! # short-circuit before import_run
1059
+ subtract_run import_run element
743
1060
  normalize!
744
1061
  end
745
1062
 
@@ -775,16 +1092,17 @@ module Net
775
1092
  # #string will be regenerated after deletion.
776
1093
  #
777
1094
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
778
- def delete?(object)
779
- tuple = input_to_tuple object
780
- if tuple.first == tuple.last
781
- return unless include_tuple? tuple
782
- tuple_subtract tuple
1095
+ def delete?(element)
1096
+ modifying! # short-circuit before import_minmax
1097
+ minmax = import_minmax element
1098
+ if minmax.first == minmax.last
1099
+ return unless include_minmax? minmax
1100
+ subtract_minmax minmax
783
1101
  normalize!
784
- from_tuple_int tuple.first
1102
+ export_num minmax.first
785
1103
  else
786
1104
  copy = dup
787
- tuple_subtract tuple
1105
+ subtract_minmax minmax
788
1106
  normalize!
789
1107
  copy if copy.subtract(self).valid?
790
1108
  end
@@ -816,37 +1134,34 @@ module Net
816
1134
  #
817
1135
  # Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference
818
1136
  def slice!(index, length = nil)
1137
+ modifying! # short-circuit before slice
819
1138
  deleted = slice(index, length) and subtract deleted
820
1139
  deleted
821
1140
  end
822
1141
 
823
- # Merges all of the elements that appear in any of the +inputs+ into the
824
- # set, and returns +self+.
1142
+ # In-place set #union. Merges all of the elements that appear in any of
1143
+ # the +sets+ into this set, and returns +self+.
825
1144
  #
826
- # The +inputs+ may be any objects that would be accepted by ::new:
827
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
828
- # formatted strings, other sequence sets, or enumerables containing any of
829
- # these.
1145
+ # The +sets+ may be any objects that would be accepted by ::new.
830
1146
  #
831
- # #string will be regenerated after all inputs have been merged.
1147
+ # #string will be regenerated after all sets have been merged.
832
1148
  #
833
1149
  # Related: #add, #add?, #union
834
- def merge(*inputs)
835
- tuples_add input_to_tuples inputs
1150
+ def merge(*sets)
1151
+ modifying! # short-circuit before import_runs
1152
+ add_runs import_runs sets
836
1153
  normalize!
837
1154
  end
838
1155
 
839
- # Removes all of the elements that appear in any of the given +objects+
840
- # from the set, and returns +self+.
1156
+ # In-place set #difference. Removes all of the elements that appear in
1157
+ # any of the given +sets+ from this set, and returns +self+.
841
1158
  #
842
- # The +objects+ may be any objects that would be accepted by ::new:
843
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
844
- # formatted strings, other sequence sets, or enumerables containing any of
845
- # these.
1159
+ # The +sets+ may be any objects that would be accepted by ::new.
846
1160
  #
847
1161
  # Related: #difference
848
- def subtract(*objects)
849
- tuples_subtract input_to_tuples objects
1162
+ def subtract(*sets)
1163
+ modifying! # short-circuit before import_runs
1164
+ subtract_runs import_runs sets
850
1165
  normalize!
851
1166
  end
852
1167
 
@@ -858,21 +1173,21 @@ module Net
858
1173
  # This is useful when the given order is significant, for example in a
859
1174
  # ESEARCH response to IMAP#sort.
860
1175
  #
1176
+ # See SequenceSet@Ordered+and+Normalized+sets.
1177
+ #
861
1178
  # Related: #each_entry, #elements
862
1179
  def entries; each_entry.to_a end
863
1180
 
864
1181
  # Returns an array of ranges and integers and <tt>:*</tt>.
865
1182
  #
866
1183
  # The returned elements are sorted and coalesced, even when the input
867
- # #string is not. <tt>*</tt> will sort last. See #normalize.
1184
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
1185
+ # SequenceSet@Ordered+and+Normalized+sets.
868
1186
  #
869
1187
  # By itself, <tt>*</tt> translates to <tt>:*</tt>. A range containing
870
1188
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
871
1189
  # cases to a maximum value.
872
1190
  #
873
- # The returned elements will be sorted and coalesced, even when the input
874
- # #string is not. <tt>*</tt> will sort last. See #normalize.
875
- #
876
1191
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
877
1192
  # #=> [2, 5..9, 11..12, :*]
878
1193
  #
@@ -883,15 +1198,13 @@ module Net
883
1198
  # Returns an array of ranges
884
1199
  #
885
1200
  # The returned elements are sorted and coalesced, even when the input
886
- # #string is not. <tt>*</tt> will sort last. See #normalize.
1201
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
1202
+ # SequenceSet@Ordered+and+Normalized+sets.
887
1203
  #
888
1204
  # <tt>*</tt> translates to an endless range. By itself, <tt>*</tt>
889
1205
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
890
1206
  # value.
891
1207
  #
892
- # The returned ranges will be sorted and coalesced, even when the input
893
- # #string is not. <tt>*</tt> will sort last. See #normalize.
894
- #
895
1208
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
896
1209
  # #=> [2..2, 5..9, 11..12, :*..]
897
1210
  # Net::IMAP::SequenceSet["123,999:*,456:789"].ranges
@@ -903,7 +1216,7 @@ module Net
903
1216
  # Returns a sorted array of all of the number values in the sequence set.
904
1217
  #
905
1218
  # The returned numbers are sorted and de-duplicated, even when the input
906
- # #string is not. See #normalize.
1219
+ # #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
907
1220
  #
908
1221
  # Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
909
1222
  # #=> [2, 5, 6, 7, 8, 9, 11, 12]
@@ -935,54 +1248,34 @@ module Net
935
1248
  # no sorting, deduplication, or coalescing. When #string is in its
936
1249
  # normalized form, this will yield the same values as #each_element.
937
1250
  #
1251
+ # See SequenceSet@Ordered+and+Normalized+sets.
1252
+ #
938
1253
  # Related: #entries, #each_element
939
1254
  def each_entry(&block) # :yields: integer or range or :*
940
1255
  return to_enum(__method__) unless block_given?
941
- each_entry_tuple do yield tuple_to_entry _1 end
1256
+ each_entry_run do yield export_run_entry _1 end
942
1257
  end
943
1258
 
944
1259
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
945
1260
  # and returns self. Returns an enumerator when called without a block.
946
1261
  #
947
1262
  # The returned numbers are sorted and de-duplicated, even when the input
948
- # #string is not. See #normalize.
1263
+ # #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
949
1264
  #
950
1265
  # Related: #elements, #each_entry
951
1266
  def each_element # :yields: integer or range or :*
952
1267
  return to_enum(__method__) unless block_given?
953
- @tuples.each do yield tuple_to_entry _1 end
954
- self
955
- end
956
-
957
- private
958
-
959
- def each_entry_tuple(&block)
960
- return to_enum(__method__) unless block_given?
961
- if @string
962
- @string.split(",") do block.call str_to_tuple _1 end
963
- else
964
- @tuples.each(&block)
965
- end
1268
+ runs.each do yield export_run_entry _1 end
966
1269
  self
967
1270
  end
968
1271
 
969
- def tuple_to_entry((min, max))
970
- if min == STAR_INT then :*
971
- elsif max == STAR_INT then min..
972
- elsif min == max then min
973
- else min..max
974
- end
975
- end
976
-
977
- public
978
-
979
1272
  # Yields each range in #ranges to the block and returns self.
980
1273
  # Returns an enumerator when called without a block.
981
1274
  #
982
1275
  # Related: #ranges
983
1276
  def each_range # :yields: range
984
1277
  return to_enum(__method__) unless block_given?
985
- @tuples.each do |min, max|
1278
+ minmaxes.each do |min, max|
986
1279
  if min == STAR_INT then yield :*..
987
1280
  elsif max == STAR_INT then yield min..
988
1281
  else yield min..max
@@ -1001,7 +1294,7 @@ module Net
1001
1294
  def each_number(&block) # :yields: integer
1002
1295
  return to_enum(__method__) unless block_given?
1003
1296
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
1004
- @tuples.each do each_number_in_tuple _1, _2, &block end
1297
+ minmaxes.each do each_number_in_minmax _1, _2, &block end
1005
1298
  self
1006
1299
  end
1007
1300
 
@@ -1015,16 +1308,7 @@ module Net
1015
1308
  def each_ordered_number(&block)
1016
1309
  return to_enum(__method__) unless block_given?
1017
1310
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
1018
- each_entry_tuple do each_number_in_tuple _1, _2, &block end
1019
- end
1020
-
1021
- private def each_number_in_tuple(min, max, &block)
1022
- if min == STAR_INT then yield :*
1023
- elsif min == max then yield min
1024
- elsif max != STAR_INT then (min..max).each(&block)
1025
- else
1026
- raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1027
- end
1311
+ each_entry_minmax do each_number_in_minmax _1, _2, &block end
1028
1312
  end
1029
1313
 
1030
1314
  # Returns a Set with all of the #numbers in the sequence set.
@@ -1036,35 +1320,103 @@ module Net
1036
1320
  # Related: #elements, #ranges, #numbers
1037
1321
  def to_set; Set.new(numbers) end
1038
1322
 
1039
- # Returns the count of #numbers in the set.
1323
+ # Returns the number of members in the set.
1324
+ #
1325
+ # Unlike #count, <tt>"*"</tt> is considered to be distinct from
1326
+ # <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1327
+ #
1328
+ # set = Net::IMAP::SequenceSet[1..10]
1329
+ # set.count #=> 10
1330
+ # set.cardinality #=> 10
1331
+ #
1332
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1333
+ # set.count #=> 1
1334
+ # set.cardinality #=> 2
1335
+ #
1336
+ # set = Net::IMAP::SequenceSet[1..]
1337
+ # set.count #=> 4294967295
1338
+ # set.cardinality #=> 4294967296
1339
+ #
1340
+ # Related: #count, #count_with_duplicates
1341
+ def cardinality = minmaxes.sum(runs.count) { _2 - _1 }
1342
+
1343
+ # Returns the count of distinct #numbers in the set.
1344
+ #
1345
+ # Unlike #cardinality, <tt>"*"</tt> is considered to be equal to
1346
+ # <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1040
1347
  #
1041
- # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1042
- # unsigned integer value).
1348
+ # set = Net::IMAP::SequenceSet[1..10]
1349
+ # set.count #=> 10
1350
+ # set.cardinality #=> 10
1043
1351
  #
1044
- # Related: #count_with_duplicates
1352
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1353
+ # set.count #=> 1
1354
+ # set.cardinality #=> 2
1355
+ #
1356
+ # set = Net::IMAP::SequenceSet[1..]
1357
+ # set.count #=> 4294967295
1358
+ # set.cardinality #=> 4294967296
1359
+ #
1360
+ # Related: #cardinality, #count_with_duplicates
1045
1361
  def count
1046
- @tuples.sum(@tuples.count) { _2 - _1 } +
1047
- (include_star? && include?(UINT32_MAX) ? -1 : 0)
1362
+ cardinality + (include_star? && include?(UINT32_MAX) ? -1 : 0)
1048
1363
  end
1049
1364
 
1050
- alias size count
1051
-
1052
1365
  # Returns the count of numbers in the ordered #entries, including any
1053
1366
  # repeated numbers.
1054
1367
  #
1055
- # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1056
- # unsigned integer value).
1057
- #
1058
- # When #string is normalized, this behaves the same as #count.
1059
- #
1060
- # Related: #entries, #count_duplicates, #has_duplicates?
1368
+ # When #string is normalized, this returns the same as #count. Like
1369
+ # #count, <tt>"*"</tt> is considered to be equal to <tt>2³² - 1</tt> (the
1370
+ # maximum 32-bit unsigned integer value).
1371
+ #
1372
+ # In a range, <tt>"*"</tt> is _not_ considered a duplicate:
1373
+ # set = Net::IMAP::SequenceSet["4294967295:*"]
1374
+ # set.count_with_duplicates #=> 1
1375
+ # set.size #=> 2
1376
+ # set.count #=> 1
1377
+ # set.cardinality #=> 2
1378
+ #
1379
+ # In a separate entry, <tt>"*"</tt> _is_ considered a duplicate:
1380
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1381
+ # set.count_with_duplicates #=> 2
1382
+ # set.size #=> 2
1383
+ # set.count #=> 1
1384
+ # set.cardinality #=> 2
1385
+ #
1386
+ # Related: #count, #cardinality, #size, #count_duplicates,
1387
+ # #has_duplicates?, #entries
1061
1388
  def count_with_duplicates
1062
1389
  return count unless @string
1063
- each_entry_tuple.sum {|min, max|
1390
+ each_entry_minmax.sum {|min, max|
1064
1391
  max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
1065
1392
  }
1066
1393
  end
1067
1394
 
1395
+ # Returns the combined size of the ordered #entries, including any
1396
+ # repeated numbers.
1397
+ #
1398
+ # When #string is normalized, this returns the same as #cardinality.
1399
+ # Like #cardinality, <tt>"*"</tt> is considered to be be distinct from
1400
+ # <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1401
+ #
1402
+ # set = Net::IMAP::SequenceSet["4294967295:*"]
1403
+ # set.size #=> 2
1404
+ # set.count_with_duplicates #=> 1
1405
+ # set.count #=> 1
1406
+ # set.cardinality #=> 2
1407
+ #
1408
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1409
+ # set.size #=> 2
1410
+ # set.count_with_duplicates #=> 2
1411
+ # set.count #=> 1
1412
+ # set.cardinality #=> 2
1413
+ #
1414
+ # Related: #cardinality, #count_with_duplicates, #count, #entries
1415
+ def size
1416
+ return cardinality unless @string
1417
+ each_entry_minmax.sum {|min, max| max - min + 1 }
1418
+ end
1419
+
1068
1420
  # Returns the count of repeated numbers in the ordered #entries, the
1069
1421
  # difference between #count_with_duplicates and #count.
1070
1422
  #
@@ -1082,7 +1434,7 @@ module Net
1082
1434
  #
1083
1435
  # Always returns +false+ when #string is normalized.
1084
1436
  #
1085
- # Related: #entries, #count_with_duplicates, #count_duplicates?
1437
+ # Related: #entries, #count_with_duplicates, #count_duplicates
1086
1438
  def has_duplicates?
1087
1439
  return false unless @string
1088
1440
  count_with_duplicates != count
@@ -1093,10 +1445,10 @@ module Net
1093
1445
  #
1094
1446
  # Related: #[], #at, #find_ordered_index
1095
1447
  def find_index(number)
1096
- number = to_tuple_int number
1097
- each_tuple_with_index(@tuples) do |min, max, idx_min|
1448
+ number = import_num number
1449
+ each_minmax_with_index(minmaxes) do |min, max, idx_min|
1098
1450
  number < min and return nil
1099
- number <= max and return from_tuple_int(idx_min + (number - min))
1451
+ number <= max and return export_num(idx_min + (number - min))
1100
1452
  end
1101
1453
  nil
1102
1454
  end
@@ -1106,38 +1458,15 @@ module Net
1106
1458
  #
1107
1459
  # Related: #find_index
1108
1460
  def find_ordered_index(number)
1109
- number = to_tuple_int number
1110
- each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1461
+ number = import_num number
1462
+ each_minmax_with_index(each_entry_minmax) do |min, max, idx_min|
1111
1463
  if min <= number && number <= max
1112
- return from_tuple_int(idx_min + (number - min))
1464
+ return export_num(idx_min + (number - min))
1113
1465
  end
1114
1466
  end
1115
1467
  nil
1116
1468
  end
1117
1469
 
1118
- private
1119
-
1120
- def each_tuple_with_index(tuples)
1121
- idx_min = 0
1122
- tuples.each do |min, max|
1123
- idx_max = idx_min + (max - min)
1124
- yield min, max, idx_min, idx_max
1125
- idx_min = idx_max + 1
1126
- end
1127
- idx_min
1128
- end
1129
-
1130
- def reverse_each_tuple_with_index(tuples)
1131
- idx_max = -1
1132
- tuples.reverse_each do |min, max|
1133
- yield min, max, (idx_min = idx_max - (max - min)), idx_max
1134
- idx_max = idx_min - 1
1135
- end
1136
- idx_max
1137
- end
1138
-
1139
- public
1140
-
1141
1470
  # :call-seq: at(index) -> integer or nil
1142
1471
  #
1143
1472
  # Returns the number at the given +index+ in the sorted set, without
@@ -1148,7 +1477,7 @@ module Net
1148
1477
  #
1149
1478
  # Related: #[], #slice, #ordered_at
1150
1479
  def at(index)
1151
- lookup_number_by_tuple_index(tuples, index)
1480
+ seek_number_in_minmaxes(minmaxes, index)
1152
1481
  end
1153
1482
 
1154
1483
  # :call-seq: ordered_at(index) -> integer or nil
@@ -1161,21 +1490,7 @@ module Net
1161
1490
  #
1162
1491
  # Related: #[], #slice, #ordered_at
1163
1492
  def ordered_at(index)
1164
- lookup_number_by_tuple_index(each_entry_tuple, index)
1165
- end
1166
-
1167
- private def lookup_number_by_tuple_index(tuples, index)
1168
- index = Integer(index.to_int)
1169
- if index.negative?
1170
- reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1171
- idx_min <= index and return from_tuple_int(min + (index - idx_min))
1172
- end
1173
- else
1174
- each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1175
- index <= idx_max and return from_tuple_int(min + (index - idx_min))
1176
- end
1177
- end
1178
- nil
1493
+ seek_number_in_minmaxes(each_entry_minmax, index)
1179
1494
  end
1180
1495
 
1181
1496
  # :call-seq:
@@ -1226,33 +1541,58 @@ module Net
1226
1541
 
1227
1542
  alias slice :[]
1228
1543
 
1229
- private
1230
-
1231
- def slice_length(start, length)
1232
- start = Integer(start.to_int)
1233
- length = Integer(length.to_int)
1234
- raise ArgumentError, "length must be positive" unless length.positive?
1235
- last = start + length - 1 unless start.negative? && start.abs <= length
1236
- slice_range(start..last)
1544
+ # Returns a copy of +self+ which only contains the numbers above +num+.
1545
+ #
1546
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(10) # to_s => "11:22,50"
1547
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(20) # to_s => "21:22,50
1548
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(30) # to_s => "50"
1549
+ #
1550
+ # This returns the same result as #intersection with <tt>((num+1)..)</tt>
1551
+ # or #difference with <tt>(..num)</tt>.
1552
+ #
1553
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (11..) # to_s => "11:22,50"
1554
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (..10) # to_s => "11:22,50"
1555
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (21..) # to_s => "21:22,50"
1556
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (..20) # to_s => "21:22,50"
1557
+ #
1558
+ # Related: #above, #-, #&
1559
+ def above(num)
1560
+ NumValidator.valid_nz_number?(num) or
1561
+ raise ArgumentError, "not a valid sequence set number"
1562
+ difference(..num)
1237
1563
  end
1238
1564
 
1239
- def slice_range(range)
1240
- first = range.begin || 0
1241
- last = range.end || -1
1242
- last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1243
- if (first * last).positive? && last < first
1244
- SequenceSet.empty
1245
- elsif (min = at(first))
1246
- max = at(last)
1247
- if max == :* then self & (min..)
1248
- elsif min <= max then self & (min..max)
1249
- else SequenceSet.empty
1250
- end
1251
- end
1565
+ # Returns a copy of +self+ which only contains numbers below +num+.
1566
+ #
1567
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(10) # to_s => "5"
1568
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(20) # to_s => "5,10:19"
1569
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22"
1570
+ #
1571
+ # This returns the same result as #intersection with <tt>(..(num-1))</tt>
1572
+ # or #difference with <tt>(num..)</tt>.
1573
+ #
1574
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (..9) # to_s => "5"
1575
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (10..) # to_s => "5"
1576
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (..19) # to_s => "5,10:19"
1577
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (20..) # to_s => "5,10:19"
1578
+ #
1579
+ # When the set does not contain <tt>*</tt>, #below is identical to #limit
1580
+ # with <tt>max: num - 1</tt>. When the set does contain <tt>*</tt>,
1581
+ # #below always drops it from the result. Use #limit when the IMAP
1582
+ # semantics for <tt>*</tt> must be enforced.
1583
+ #
1584
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22"
1585
+ # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 29) # to_s => "5,10:22"
1586
+ # Net::IMAP::SequenceSet["5,10:22,*"].below(30) # to_s => "5,10:22"
1587
+ # Net::IMAP::SequenceSet["5,10:22,*"].limit(max: 29) # to_s => "5,10:22,29"
1588
+ #
1589
+ # Related: #above, #-, #&, #limit
1590
+ def below(num)
1591
+ NumValidator.valid_nz_number?(num) or
1592
+ raise ArgumentError, "not a valid sequence set number"
1593
+ difference(num..)
1252
1594
  end
1253
1595
 
1254
- public
1255
-
1256
1596
  # Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers
1257
1597
  # and ranges over +max+ removed, and ranges containing +max+ converted to
1258
1598
  # end at +max+.
@@ -1270,8 +1610,9 @@ module Net
1270
1610
  # Net::IMAP::SequenceSet["500:*"].limit(max: 37)
1271
1611
  # #=> Net::IMAP::SequenceSet["37"]
1272
1612
  #
1613
+ # Related: #limit!
1273
1614
  def limit(max:)
1274
- max = to_tuple_int(max)
1615
+ max = import_num(max)
1275
1616
  if empty? then self.class.empty
1276
1617
  elsif !include_star? && max < min then self.class.empty
1277
1618
  elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze
@@ -1284,76 +1625,206 @@ module Net
1284
1625
  #
1285
1626
  # Related: #limit
1286
1627
  def limit!(max:)
1628
+ modifying! # short-circuit before querying
1287
1629
  star = include_star?
1288
- max = to_tuple_int(max)
1289
- tuple_subtract [max + 1, STAR_INT]
1290
- tuple_add [max, max ] if star
1630
+ max = import_num(max)
1631
+ subtract_minmax [max + 1, STAR_INT]
1632
+ add_minmax [max, max ] if star
1291
1633
  normalize!
1292
1634
  end
1293
1635
 
1294
1636
  # :call-seq: complement! -> self
1295
1637
  #
1296
- # Converts the SequenceSet to its own #complement. It will contain all
1297
- # possible values _except_ for those currently in the set.
1638
+ # In-place set #complement. Replaces the contents of this set with its
1639
+ # own #complement. It will contain all possible values _except_ for those
1640
+ # currently in the set.
1298
1641
  #
1299
1642
  # Related: #complement
1300
1643
  def complement!
1644
+ modifying! # short-circuit before querying
1301
1645
  return replace(self.class.full) if empty?
1302
1646
  return clear if full?
1303
- flat = @tuples.flat_map { [_1 - 1, _2 + 1] }
1647
+ flat = minmaxes.flat_map { [_1 - 1, _2 + 1] }
1304
1648
  if flat.first < 1 then flat.shift else flat.unshift 1 end
1305
1649
  if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end
1306
- @tuples = flat.each_slice(2).to_a
1650
+ replace_minmaxes flat.each_slice(2).to_a
1307
1651
  normalize!
1308
1652
  end
1309
1653
 
1310
- # Returns a new SequenceSet with a normalized string representation.
1654
+ # In-place set #intersection. Removes any elements that are missing from
1655
+ # +other+ from this set, keeping only the #intersection, and returns
1656
+ # +self+.
1657
+ #
1658
+ # +other+ can be any object that would be accepted by ::new.
1659
+ #
1660
+ # set = Net::IMAP::SequenceSet.new(1..5)
1661
+ # set.intersect! [2, 4, 6]
1662
+ # set #=> Net::IMAP::SequenceSet("2,4")
1663
+ #
1664
+ # Related: #intersection, #intersect?
1665
+ def intersect!(other)
1666
+ modifying! # short-circuit before processing input
1667
+ subtract SequenceSet.new(other).complement!
1668
+ end
1669
+
1670
+ # In-place set #xor. Adds any numbers in +other+ that are missing from
1671
+ # this set, removes any numbers in +other+ that are already in this set,
1672
+ # and returns +self+.
1673
+ #
1674
+ # +other+ can be any object that would be accepted by ::new.
1675
+ #
1676
+ # set = Net::IMAP::SequenceSet.new(1..5)
1677
+ # set.xor! [2, 4, 6]
1678
+ # set #=> Net::IMAP::SequenceSet["1,3,5:6"]
1679
+ #
1680
+ # Related: #xor, #merge, #subtract
1681
+ def xor!(other)
1682
+ modifying! # short-circuit before processing input
1683
+ other = SequenceSet.new(other)
1684
+ copy = dup
1685
+ merge(other).subtract(other.subtract(copy.complement!))
1686
+ end
1687
+
1688
+ # Returns whether #string is fully normalized: entries have been sorted,
1689
+ # deduplicated, and coalesced, and all entries are in normal form. See
1690
+ # SequenceSet@Ordered+and+Normalized+sets.
1691
+ #
1692
+ # Net::IMAP::SequenceSet["1,3,5"].normalized? #=> true
1693
+ # Net::IMAP::SequenceSet["20:30"].normalized? #=> true
1694
+ #
1695
+ # Net::IMAP::SequenceSet["3,5,1"].normalized? #=> false, not sorted
1696
+ # Net::IMAP::SequenceSet["1,2,3"].normalized? #=> false, not coalesced
1697
+ # Net::IMAP::SequenceSet["1:5,2"].normalized? #=> false, repeated number
1698
+ #
1699
+ # Net::IMAP::SequenceSet["1:1"].normalized? #=> false, number as range
1700
+ # Net::IMAP::SequenceSet["5:1"].normalized? #=> false, backwards range
1701
+ #
1702
+ # Returns +true+ if (and only if) #string is equal to #normalized_string:
1703
+ # seqset = Net::IMAP::SequenceSet["1:3,5"]
1704
+ # seqset.string #=> "1:3,5"
1705
+ # seqset.normalized_string #=> "1:3,5"
1706
+ # seqset.entries #=> [1..3, 5]
1707
+ # seqset.elements #=> [1..3, 5]
1708
+ # seqset.normalized? #=> true
1709
+ #
1710
+ # seqset = Net::IMAP::SequenceSet["3,1,2"]
1711
+ # seqset.string #=> "3,1,2"
1712
+ # seqset.normalized_string #=> "1:3"
1713
+ # seqset.entries #=> [3, 1, 2]
1714
+ # seqset.elements #=> [1..3]
1715
+ # seqset.normalized? #=> false
1716
+ #
1717
+ # Can return +false+ even when #entries and #elements are the same:
1718
+ # seqset = Net::IMAP::SequenceSet["5:1"]
1719
+ # seqset.string #=> "5:1"
1720
+ # seqset.normalized_string #=> "1:5"
1721
+ # seqset.entries #=> [1..5]
1722
+ # seqset.elements #=> [1..5]
1723
+ # seqset.normalized? #=> false
1724
+ #
1725
+ # Note that empty sets are normalized, even though they are not #valid?:
1726
+ # seqset = Net::IMAP::SequenceSet.empty
1727
+ # seqset.normalized? #=> true
1728
+ # seqset.valid? #=> false
1729
+ #
1730
+ # Related: #normalize, #normalize!, #normalized_string
1731
+ def normalized?
1732
+ @string.nil? || normal_string?(@string)
1733
+ end
1734
+
1735
+ # Returns a SequenceSet with a normalized string representation: entries
1736
+ # have been sorted, deduplicated, and coalesced, and all entries
1737
+ # are in normal form. Returns +self+ for frozen normalized sets, and a
1738
+ # normalized duplicate otherwise.
1311
1739
  #
1312
- # The returned set's #string is sorted and deduplicated. Adjacent or
1313
- # overlapping elements will be merged into a single larger range.
1740
+ # See SequenceSet@Ordered+and+Normalized+sets.
1314
1741
  #
1315
1742
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
1316
1743
  # #=> Net::IMAP::SequenceSet["1:7,9:11"]
1317
1744
  #
1318
- # Related: #normalize!, #normalized_string
1745
+ # Related: #normalize!, #normalized_string, #normalized?
1319
1746
  def normalize
1320
- str = normalized_string
1321
- return self if frozen? && str == string
1322
- remain_frozen dup.instance_exec { @string = str&.-@; self }
1747
+ frozen? && normalized? ? self : remain_frozen(dup.normalize!)
1323
1748
  end
1324
1749
 
1325
1750
  # Resets #string to be sorted, deduplicated, and coalesced. Returns
1326
- # +self+.
1751
+ # +self+. See SequenceSet@Ordered+and+Normalized+sets.
1327
1752
  #
1328
- # Related: #normalize, #normalized_string
1753
+ # Related: #normalize, #normalized_string, #normalized?
1329
1754
  def normalize!
1755
+ modifying! # redundant check (normalizes the error message for JRuby)
1330
1756
  @string = nil
1331
1757
  self
1332
1758
  end
1333
1759
 
1334
1760
  # Returns a normalized +sequence-set+ string representation, sorted
1335
1761
  # and deduplicated. Adjacent or overlapping elements will be merged into
1336
- # a single larger range. Returns +nil+ when the set is empty.
1762
+ # a single larger range. See SequenceSet@Ordered+and+Normalized+sets.
1337
1763
  #
1338
1764
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
1339
1765
  # #=> "1:7,9:11"
1340
1766
  #
1341
- # Related: #normalize!, #normalize
1767
+ # Returns +nil+ when the set is empty.
1768
+ #
1769
+ # Related: #normalize!, #normalize, #string, #to_s, #normalized?
1342
1770
  def normalized_string
1343
- @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
1771
+ export_runs(runs) unless runs.empty?
1344
1772
  end
1345
1773
 
1774
+ # Returns an inspection string for the SequenceSet.
1775
+ #
1776
+ # Net::IMAP::SequenceSet.new.inspect
1777
+ # #=> "Net::IMAP::SequenceSet()"
1778
+ #
1779
+ # Net::IMAP::SequenceSet(1..5, 1024, 15, 2000).inspect
1780
+ # #=> 'Net::IMAP::SequenceSet("1:5,15,1024,2000")'
1781
+ #
1782
+ # Frozen sets have slightly different output:
1783
+ #
1784
+ # Net::IMAP::SequenceSet.empty.inspect
1785
+ # #=> "Net::IMAP::SequenceSet.empty"
1786
+ #
1787
+ # Net::IMAP::SequenceSet[1..5, 1024, 15, 2000].inspect
1788
+ # #=> 'Net::IMAP::SequenceSet["1:5,15,1024,2000"]'
1789
+ #
1790
+ # Large sets (by number of #entries) have abridged output, with only the
1791
+ # first and last entries:
1792
+ #
1793
+ # Net::IMAP::SequenceSet(((1..5000) % 2).to_a).inspect
1794
+ # #=> #<Net::IMAP::SequenceSet 2500 entries "1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,...(2468 entries omitted)...,4969,4971,4973,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4995,4997,4999">
1795
+ #
1796
+ # Related: #to_s, #string
1346
1797
  def inspect
1347
- if empty?
1348
- (frozen? ? "%s.empty" : "#<%s empty>") % [self.class]
1349
- elsif frozen?
1350
- "%s[%p]" % [self.class, to_s]
1798
+ case (count = count_entries)
1799
+ when 0
1800
+ (frozen? ? "%s.empty" : "%s()") % [self.class]
1801
+ when ..INSPECT_MAX_LEN
1802
+ (frozen? ? "%s[%p]" : "%s(%p)") % [self.class, to_s]
1351
1803
  else
1352
- "#<%s %p>" % [self.class, to_s]
1804
+ if @string
1805
+ head = @string[INSPECT_ABRIDGED_HEAD_RE]
1806
+ tail = @string[INSPECT_ABRIDGED_TAIL_RE]
1807
+ else
1808
+ head = export_runs(runs.first(INSPECT_TRUNCATE_LEN)) + ","
1809
+ tail = "," + export_runs(runs.last(INSPECT_TRUNCATE_LEN))
1810
+ end
1811
+ '#<%s %d entries "%s...(%d entries omitted)...%s"%s>' % [
1812
+ self.class, count,
1813
+ head, count - INSPECT_TRUNCATE_LEN * 2, tail,
1814
+ frozen? ? " (frozen)" : "",
1815
+ ]
1353
1816
  end
1354
1817
  end
1355
1818
 
1356
- # Returns self
1819
+ ##
1820
+ # :method: to_sequence_set
1821
+ # :call-seq: to_sequence_set -> self
1822
+ #
1823
+ # Returns +self+
1824
+ #
1825
+ # Related: ::try_convert
1826
+
1827
+ # :nodoc: (work around rdoc bug)
1357
1828
  alias to_sequence_set itself
1358
1829
 
1359
1830
  # Unstable API: currently for internal use only (Net::IMAP#validate_data)
@@ -1367,49 +1838,69 @@ module Net
1367
1838
  imap.__send__(:put_string, valid_string)
1368
1839
  end
1369
1840
 
1841
+ # For YAML serialization
1842
+ def encode_with(coder) # :nodoc:
1843
+ # we can perfectly reconstruct from the string
1844
+ coder['string'] = to_s
1845
+ end
1846
+
1847
+ # For YAML deserialization
1848
+ def init_with(coder) # :nodoc:
1849
+ @set_data = new_set_data
1850
+ self.string = coder['string']
1851
+ end
1852
+
1853
+ # :stopdoc:
1370
1854
  protected
1371
1855
 
1372
- attr_reader :tuples # :nodoc:
1856
+ attr_reader :set_data
1857
+
1858
+ alias runs set_data
1859
+ alias minmaxes runs
1373
1860
 
1374
1861
  private
1375
1862
 
1376
1863
  def remain_frozen(set) frozen? ? set.freeze : set end
1864
+ def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
1377
1865
 
1378
1866
  # frozen clones are shallow copied
1379
1867
  def initialize_clone(other)
1380
- other.frozen? ? super : initialize_dup(other)
1868
+ @set_data = other.dup_set_data unless other.frozen?
1869
+ super
1381
1870
  end
1382
1871
 
1383
1872
  def initialize_dup(other)
1384
- @tuples = other.tuples.map(&:dup)
1385
- @string = other.string&.-@
1873
+ @set_data = other.dup_set_data
1386
1874
  super
1387
1875
  end
1388
1876
 
1389
- def input_to_tuple(obj)
1390
- obj = input_try_convert obj
1391
- case obj
1392
- when *STARS, Integer then [int = to_tuple_int(obj), int]
1393
- when Range then range_to_tuple(obj)
1394
- when String then str_to_tuple(obj)
1877
+ ######################################################################{{{2
1878
+ # Import methods
1879
+
1880
+ def import_minmax(input)
1881
+ entry = input_try_convert input
1882
+ case entry
1883
+ when *STARS, Integer then [int = import_num(entry), int]
1884
+ when Range then import_range_minmax(entry)
1885
+ when String then parse_minmax(entry)
1395
1886
  else
1396
- raise DataFormatError, "expected number or range, got %p" % [obj]
1887
+ raise DataFormatError, "expected number or range, got %p" % [input]
1397
1888
  end
1398
1889
  end
1890
+ alias import_run import_minmax
1399
1891
 
1400
- def input_to_tuples(obj)
1401
- obj = input_try_convert obj
1402
- case obj
1403
- when *STARS, Integer, Range then [input_to_tuple(obj)]
1404
- when String then str_to_tuples obj
1405
- when SequenceSet then obj.tuples
1406
- when Set then obj.map { [to_tuple_int(_1)] * 2 }
1407
- when Array then obj.flat_map { input_to_tuples _1 }
1892
+ def import_runs(input)
1893
+ set = input_try_convert input
1894
+ case set
1895
+ when *STARS, Integer, Range then [import_run(set)]
1896
+ when String then parse_runs set
1897
+ when SequenceSet then set.runs
1898
+ when Set then set.map { [import_num(_1)] * 2 }
1899
+ when Array then set.flat_map { import_runs _1 }
1408
1900
  when nil then []
1409
1901
  else
1410
- raise DataFormatError,
1411
- "expected nz-number, range, string, or enumerable; " \
1412
- "got %p" % [obj]
1902
+ raise DataFormatError, "expected nz-number, range, '*', Set, Array; " \
1903
+ "got %p" % [input]
1413
1904
  end
1414
1905
  end
1415
1906
 
@@ -1422,9 +1913,9 @@ module Net
1422
1913
  input
1423
1914
  end
1424
1915
 
1425
- def range_to_tuple(range)
1426
- first = to_tuple_int(range.begin || 1)
1427
- last = to_tuple_int(range.end || :*)
1916
+ def import_range_minmax(range)
1917
+ first = import_num(range.begin || 1)
1918
+ last = import_num(range.end || :*)
1428
1919
  last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1429
1920
  unless first <= last
1430
1921
  raise DataFormatError, "invalid range for sequence-set: %p" % [range]
@@ -1432,67 +1923,260 @@ module Net
1432
1923
  [first, last]
1433
1924
  end
1434
1925
 
1435
- def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
1436
- def from_tuple_int(num) num == STAR_INT ? :* : num end
1926
+ def import_num(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
1927
+ def nz_number(num) = NumValidator.coerce_nz_number(num)
1928
+
1929
+ ######################################################################{{{2
1930
+ # Export methods
1931
+
1932
+ def export_num(num) num == STAR_INT ? :* : num end
1933
+
1934
+ def export_minmaxes(minmaxes)
1935
+ -minmaxes.map { export_minmax _1 }.join(",")
1936
+ end
1937
+
1938
+ def export_minmax(minmax) minmax.uniq.map { export_num _1 }.join(":") end
1939
+
1940
+ alias export_runs export_minmaxes
1941
+ alias export_run export_minmax
1942
+
1943
+ def export_minmax_entry((min, max))
1944
+ if min == STAR_INT then :*
1945
+ elsif max == STAR_INT then min..
1946
+ elsif min == max then min
1947
+ else min..max
1948
+ end
1949
+ end
1950
+ alias export_run_entry export_minmax_entry
1951
+
1952
+ def each_number_in_minmax(min, max, &block)
1953
+ if min == STAR_INT then yield :*
1954
+ elsif min == max then yield min
1955
+ elsif max != STAR_INT then (min..max).each(&block)
1956
+ else
1957
+ raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1958
+ end
1959
+ end
1960
+
1961
+ ######################################################################{{{2
1962
+ # Parse methods
1437
1963
 
1438
- def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
1439
- def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
1440
- def str_to_tuple(str)
1964
+ def parse_runs(str) str.split(",", -1).map! { parse_run _1 } end
1965
+ def parse_minmax(str) parse_entry(str).minmax end
1966
+ alias parse_run parse_minmax
1967
+
1968
+ def parse_entry(str)
1441
1969
  raise DataFormatError, "invalid sequence set string" if str.empty?
1442
- str.split(":", 2).map! { to_tuple_int _1 }.minmax
1970
+ str.split(":", 2).map! { import_num _1 }
1971
+ end
1972
+
1973
+ # yields validated but unsorted [num] or [num, num]
1974
+ def each_parsed_entry(str)
1975
+ return to_enum(__method__, str) unless block_given?
1976
+ str&.split(",", -1) do |entry| yield parse_entry(entry) end
1977
+ end
1978
+
1979
+ def normal_string?(str) normalized_entries? each_parsed_entry str end
1980
+
1981
+ def normalized_entries?(entries)
1982
+ max = nil
1983
+ entries.each do |first, last|
1984
+ return false if last && last <= first # 1:1 or 2:1
1985
+ return false if max && first <= max + 1 # 2,1 or 1,1 or 1,2
1986
+ max = last || first
1987
+ end
1988
+ true
1989
+ end
1990
+
1991
+ ######################################################################{{{2
1992
+ # Ordered entry methods
1993
+
1994
+ def count_entries
1995
+ @string ? @string.count(",") + 1 : runs.count
1996
+ end
1997
+
1998
+ def each_entry_minmax(&block)
1999
+ return to_enum(__method__) unless block_given?
2000
+ if @string
2001
+ @string.split(",") do block.call parse_minmax _1 end
2002
+ else
2003
+ minmaxes.each(&block)
2004
+ end
2005
+ self
1443
2006
  end
2007
+ alias each_entry_run each_entry_minmax
2008
+
2009
+ ######################################################################{{{2
2010
+ # Search methods
1444
2011
 
1445
- def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end
2012
+ def include_minmax?((min, max)) bsearch_range(min)&.cover?(min..max) end
1446
2013
 
1447
- def intersect_tuple?((min, max))
1448
- range = range_gte_to(min) and
2014
+ def intersect_minmax?((min, max))
2015
+ range = bsearch_range(min) and
1449
2016
  range.include?(min) || range.include?(max) || (min..max).cover?(range)
1450
2017
  end
1451
2018
 
2019
+ alias include_run? include_minmax?
2020
+ alias intersect_run? intersect_minmax?
2021
+
2022
+ def bsearch_index(num) = minmaxes.bsearch_index { _2 >= num }
2023
+ def bsearch_minmax(num) = minmaxes.bsearch { _2 >= num }
2024
+ def bsearch_range(num) = (min, max = bsearch_minmax(num)) && (min..max)
2025
+
2026
+ ######################################################################{{{2
2027
+ # Number indexing methods
2028
+
2029
+ def seek_number_in_minmaxes(minmaxes, index)
2030
+ index = Integer(index.to_int)
2031
+ if index.negative?
2032
+ reverse_each_minmax_with_index(minmaxes) do |min, max, idx_min, idx_max|
2033
+ idx_min <= index and return export_num(min + (index - idx_min))
2034
+ end
2035
+ else
2036
+ each_minmax_with_index(minmaxes) do |min, _, idx_min, idx_max|
2037
+ index <= idx_max and return export_num(min + (index - idx_min))
2038
+ end
2039
+ end
2040
+ nil
2041
+ end
2042
+
2043
+ def each_minmax_with_index(minmaxes)
2044
+ idx_min = 0
2045
+ minmaxes.each do |min, max|
2046
+ idx_max = idx_min + (max - min)
2047
+ yield min, max, idx_min, idx_max
2048
+ idx_min = idx_max + 1
2049
+ end
2050
+ idx_min
2051
+ end
2052
+
2053
+ def reverse_each_minmax_with_index(minmaxes)
2054
+ idx_max = -1
2055
+ minmaxes.reverse_each do |min, max|
2056
+ yield min, max, (idx_min = idx_max - (max - min)), idx_max
2057
+ idx_max = idx_min - 1
2058
+ end
2059
+ idx_max
2060
+ end
2061
+
2062
+ def slice_length(start, length)
2063
+ start = Integer(start.to_int)
2064
+ length = Integer(length.to_int)
2065
+ raise ArgumentError, "length must be positive" unless length.positive?
2066
+ last = start + length - 1 unless start.negative? && start.abs <= length
2067
+ slice_range(start..last)
2068
+ end
2069
+
2070
+ def slice_range(range)
2071
+ first = range.begin || 0
2072
+ last = range.end || -1
2073
+ if range.exclude_end?
2074
+ return remain_frozen_empty if last.zero?
2075
+ last -= 1 if range.end && last != STAR_INT
2076
+ end
2077
+ if (first * last).positive? && last < first
2078
+ remain_frozen_empty
2079
+ elsif (min = at(first))
2080
+ max = at(last)
2081
+ max = :* if max.nil?
2082
+ if max == :* then self & (min..)
2083
+ elsif min <= max then self & (min..max)
2084
+ else remain_frozen_empty
2085
+ end
2086
+ end
2087
+ end
2088
+
2089
+ ######################################################################{{{2
2090
+ # Core set data create/freeze/dup primitives
2091
+
2092
+ def new_set_data = []
2093
+ def freeze_set_data = set_data.each(&:freeze).freeze
2094
+ def dup_set_data = set_data.map { _1.dup }
2095
+ protected :dup_set_data
2096
+
2097
+ ######################################################################{{{2
2098
+ # Core set data query/enumeration primitives
2099
+
2100
+ def min_num = minmaxes.first&.first
2101
+ def max_num = minmaxes.last&.last
2102
+
2103
+ def min_at(idx) = minmaxes[idx][0]
2104
+ def max_at(idx) = minmaxes[idx][1]
2105
+
2106
+ ######################################################################{{{2
2107
+ # Core set data modification primitives
2108
+
2109
+ def set_min_at(idx, min) = minmaxes[idx][0] = min
2110
+ def set_max_at(idx, max) = minmaxes[idx][1] = max
2111
+ def replace_minmaxes(other) = minmaxes.replace(other)
2112
+ def append_minmax(min, max) = minmaxes << [min, max]
2113
+ def insert_minmax(idx, min, max) = minmaxes.insert idx, [min, max]
2114
+ def delete_run_at(idx) = runs.delete_at(idx)
2115
+ def slice_runs!(...) = runs.slice!(...)
2116
+ def truncate_runs!(idx) = runs.slice!(idx..)
2117
+
2118
+ ######################################################################{{{2
2119
+ # Update methods
2120
+
1452
2121
  def modifying!
1453
2122
  if frozen?
1454
2123
  raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
1455
2124
  end
1456
2125
  end
1457
2126
 
1458
- def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
1459
- def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
2127
+ def add_minmaxes(minmaxes)
2128
+ minmaxes.each do |minmax|
2129
+ add_minmax minmax
2130
+ end
2131
+ self
2132
+ end
2133
+
2134
+ def subtract_minmaxes(minmaxes)
2135
+ minmaxes.each do |minmax|
2136
+ subtract_minmax minmax
2137
+ end
2138
+ self
2139
+ end
1460
2140
 
1461
2141
  #
1462
- # --|=====| |=====new tuple=====| append
1463
- # ?????????-|=====new tuple=====|-|===lower===|-- insert
2142
+ # --|=====| |=====new run=======| append
2143
+ # ?????????-|=====new run=======|-|===lower===|-- insert
1464
2144
  #
1465
- # |=====new tuple=====|
2145
+ # |=====new run=======|
1466
2146
  # ---------??=======lower=======??--------------- noop
1467
2147
  #
1468
2148
  # ---------??===lower==|--|==| join remaining
1469
2149
  # ---------??===lower==|--|==|----|===upper===|-- join until upper
1470
2150
  # ---------??===lower==|--|==|--|=====upper===|-- join to upper
1471
- def tuple_add(tuple)
2151
+ def add_minmax(minmax)
1472
2152
  modifying!
1473
- min, max = tuple
1474
- lower, lower_idx = tuple_gte_with_index(min - 1)
1475
- if lower.nil? then tuples << [min, max]
1476
- elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1477
- else tuple_coalesce(lower, lower_idx, min, max)
2153
+ min, max = minmax
2154
+ lower_idx = bsearch_index(min - 1)
2155
+ lmin, lmax = min_at(lower_idx), max_at(lower_idx) if lower_idx
2156
+ if lmin.nil? then append_minmax min, max
2157
+ elsif (max + 1) < lmin then insert_minmax lower_idx, min, max
2158
+ else add_coalesced_minmax(lower_idx, lmin, lmax, min, max)
1478
2159
  end
1479
2160
  end
1480
2161
 
1481
- def tuple_coalesce(lower, lower_idx, min, max)
1482
- return if lower.first <= min && max <= lower.last
1483
- lower[0] = [min, lower.first].min
1484
- lower[1] = [max, lower.last].max
1485
- lower_idx += 1
1486
- return if lower_idx == tuples.count
1487
- tmax_adj = lower.last + 1
1488
- upper, upper_idx = tuple_gte_with_index(tmax_adj)
1489
- if upper
1490
- tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last)
2162
+ def add_coalesced_minmax(lower_idx, lmin, lmax, min, max)
2163
+ return if lmin <= min && max <= lmax
2164
+ set_min_at lower_idx, (lmin = min) if min < lmin
2165
+ set_max_at lower_idx, (lmax = max) if lmax < max
2166
+ next_idx = lower_idx + 1
2167
+ return if next_idx == runs.count
2168
+ tmax_adj = lmax + 1
2169
+ if (upper_idx = bsearch_index(tmax_adj))
2170
+ if tmax_adj < min_at(upper_idx)
2171
+ upper_idx -= 1
2172
+ else
2173
+ set_max_at lower_idx, max_at(upper_idx)
2174
+ end
1491
2175
  end
1492
- tuples.slice!(lower_idx..upper_idx)
2176
+ slice_runs! next_idx..upper_idx
1493
2177
  end
1494
2178
 
1495
- # |====tuple================|
2179
+ # |====subtracted run=======|
1496
2180
  # --|====| no more 1. noop
1497
2181
  # --|====|---------------------------|====lower====|-- 2. noop
1498
2182
  # -------|======lower================|---------------- 3. split
@@ -1505,61 +2189,59 @@ module Net
1505
2189
  # -------??=====lower====|--|====| no more 6. delete rest
1506
2190
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
1507
2191
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
1508
- def tuple_subtract(tuple)
2192
+ def subtract_minmax(minmax)
1509
2193
  modifying!
1510
- min, max = tuple
1511
- lower, idx = tuple_gte_with_index(min)
1512
- if lower.nil? then nil # case 1.
1513
- elsif max < lower.first then nil # case 2.
1514
- elsif max < lower.last then tuple_trim_or_split lower, idx, min, max
1515
- else tuples_trim_or_delete lower, idx, min, max
2194
+ min, max = minmax
2195
+ idx = bsearch_index(min)
2196
+ lmin, lmax = min_at(idx), max_at(idx) if idx
2197
+ if lmin.nil? then nil # case 1.
2198
+ elsif max < lmin then nil # case 2.
2199
+ elsif max < lmax then trim_or_split_minmax idx, lmin, min, max
2200
+ else trim_or_delete_minmax idx, lmin, lmax, min, max
1516
2201
  end
1517
2202
  end
1518
2203
 
1519
- def tuple_trim_or_split(lower, idx, tmin, tmax)
1520
- if lower.first < tmin # split
1521
- tuples.insert(idx, [lower.first, tmin - 1])
2204
+ def trim_or_split_minmax(idx, lmin, tmin, tmax)
2205
+ set_min_at idx, tmax + 1
2206
+ if lmin < tmin # split
2207
+ insert_minmax idx, lmin, tmin - 1
1522
2208
  end
1523
- lower[0] = tmax + 1
1524
2209
  end
1525
2210
 
1526
- def tuples_trim_or_delete(lower, lower_idx, tmin, tmax)
1527
- if lower.first < tmin # trim lower
1528
- lower[1] = tmin - 1
2211
+ def trim_or_delete_minmax(lower_idx, lmin, lmax, tmin, tmax)
2212
+ if lmin < tmin # trim lower
2213
+ lmax = set_max_at lower_idx, tmin - 1
1529
2214
  lower_idx += 1
1530
2215
  end
1531
- if tmax == lower.last # case 5
1532
- upper_idx = lower_idx
1533
- elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1))
1534
- upper_idx -= 1 # cases 7 and 8
1535
- upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7)
2216
+ if tmax == lmax # case 5
2217
+ delete_run_at lower_idx
2218
+ elsif (upper_idx = bsearch_index(tmax + 1))
2219
+ if min_at(upper_idx) <= tmax # case 8
2220
+ set_min_at upper_idx, tmax + 1
2221
+ end
2222
+ slice_runs! lower_idx..upper_idx - 1 # cases 7 and 8
2223
+ else # case 6
2224
+ truncate_runs! lower_idx
1536
2225
  end
1537
- tuples.slice!(lower_idx..upper_idx)
1538
2226
  end
1539
2227
 
1540
- def tuple_gte_with_index(num)
1541
- idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx]
1542
- end
1543
-
1544
- def range_gte_to(num)
1545
- first, last = tuples.bsearch { _2 >= num }
1546
- first..last if first
1547
- end
1548
-
1549
- def nz_number(num)
1550
- String === num && !/\A[1-9]\d*\z/.match?(num) and
1551
- raise DataFormatError, "%p is not a valid nz-number" % [num]
1552
- NumValidator.ensure_nz_number Integer num
1553
- rescue TypeError # To catch errors from Integer()
1554
- raise DataFormatError, $!.message
1555
- end
2228
+ alias add_runs add_minmaxes
2229
+ alias add_run add_minmax
2230
+ alias subtract_runs subtract_minmaxes
2231
+ alias subtract_run subtract_minmax
1556
2232
 
2233
+ ######################################################################{{{2
1557
2234
  # intentionally defined after the class implementation
1558
2235
 
2236
+ FULL_SET_DATA = [[1, STAR_INT].freeze].freeze
2237
+ private_constant :FULL_SET_DATA
2238
+
1559
2239
  EMPTY = new.freeze
1560
2240
  FULL = self["1:*"]
1561
2241
  private_constant :EMPTY, :FULL
1562
2242
 
2243
+ # }}}
2244
+ # vim:foldmethod=marker
1563
2245
  end
1564
2246
  end
1565
2247
  end