net-imap 0.5.12 → 0.6.1

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.
@@ -145,6 +145,10 @@ module Net
145
145
  # #entries and #elements are identical. Use #append to preserve #entries
146
146
  # order while modifying a set.
147
147
  #
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.
151
+ #
148
152
  # == Using <tt>*</tt>
149
153
  #
150
154
  # \IMAP sequence sets may contain a special value <tt>"*"</tt>, which
@@ -179,7 +183,7 @@ module Net
179
183
  #
180
184
  # When a set includes <tt>*</tt>, some methods may have surprising behavior.
181
185
  #
182
- # For example, #complement treats <tt>*</tt> as its own number. This way,
186
+ # For example, #complement treats <tt>*</tt> as its own member. This way,
183
187
  # the #intersection of a set and its #complement will always be empty. And
184
188
  # <tt>*</tt> is sorted as greater than any other number in the set. This is
185
189
  # not how an \IMAP server interprets the set: it will convert <tt>*</tt> to
@@ -199,7 +203,7 @@ module Net
199
203
  # (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4]
200
204
  #
201
205
  # When counting the number of numbers in a set, <tt>*</tt> will be counted
202
- # _except_ when UINT32_MAX is also in the set:
206
+ # as if it were equal to UINT32_MAX:
203
207
  # UINT32_MAX = 2**32 - 1
204
208
  # Net::IMAP::SequenceSet["*"].count => 1
205
209
  # Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX
@@ -208,6 +212,12 @@ module Net
208
212
  # Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1
209
213
  # Net::IMAP::SequenceSet[UINT32_MAX..].count => 1
210
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
+ #
211
221
  # == What's here?
212
222
  #
213
223
  # SequenceSet provides methods for:
@@ -271,8 +281,10 @@ module Net
271
281
  # occurrence in entries.
272
282
  #
273
283
  # <i>Set cardinality:</i>
274
- # - #count (aliased as #size): Returns the count of numbers in the set.
275
- # 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.
276
288
  # - #empty?: Returns whether the set has no members. \IMAP syntax does not
277
289
  # allow empty sequence sets.
278
290
  # - #valid?: Returns whether the set has any members.
@@ -280,12 +292,18 @@ module Net
280
292
  # <tt>*</tt>.
281
293
  #
282
294
  # <i>Denormalized properties:</i>
295
+ # - #normalized?: Returns whether #entries are sorted, deduplicated, and
296
+ # coalesced, and all #string entries are in normalized form.
283
297
  # - #has_duplicates?: Returns whether the ordered entries repeat any
284
298
  # numbers.
285
- # - #count_duplicates: Returns the count of repeated numbers in the ordered
286
- # 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.
287
302
  # - #count_with_duplicates: Returns the count of numbers in the ordered
288
- # 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.
289
307
  #
290
308
  # === Methods for Iterating
291
309
  #
@@ -332,7 +350,7 @@ module Net
332
350
  # given maximum value and removed all members over that maximum.
333
351
  #
334
352
  # === Methods for Assigning
335
- # These methods add or replace elements in +self+.
353
+ # These methods add or replace numbers in +self+.
336
354
  #
337
355
  # <i>Normalized (sorted and coalesced):</i>
338
356
  #
@@ -341,8 +359,12 @@ module Net
341
359
  # - #add (aliased as #<<): Adds a given element to the set; returns +self+.
342
360
  # - #add?: If the given element is not fully included the set, adds it and
343
361
  # returns +self+; otherwise, returns +nil+.
344
- # - #merge: Adds all members of the given sets into this set; returns +self+.
345
- # - #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+.
346
368
  #
347
369
  # <i>Order preserving:</i>
348
370
  #
@@ -355,7 +377,7 @@ module Net
355
377
  # of a given object.
356
378
  #
357
379
  # === Methods for Deleting
358
- # 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
359
381
  # sorted and coalesced.
360
382
  #
361
383
  # - #clear: Removes all elements in the set; returns +self+.
@@ -363,10 +385,12 @@ module Net
363
385
  # - #delete?: If the given element is included in the set, removes it and
364
386
  # returns it; otherwise, returns +nil+.
365
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+.
366
390
  # - #slice!: Removes the number or consecutive numbers at a given offset or
367
391
  # range of offsets.
368
- # - #subtract: Removes all members of the given sets from this set; returns
369
- # +self+.
392
+ # - #subtract: In-place set #difference. Removes all members of the given
393
+ # sets from this set; returns +self+.
370
394
  # - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
371
395
  # members over that maximum; returns +self+.
372
396
  #
@@ -531,12 +555,17 @@ module Net
531
555
  # combined with set operations (#|, #&, #^, #-, etc) to make new sets.
532
556
  #
533
557
  # See SequenceSet@Creating+sequence+sets.
534
- def initialize(input = nil) input ? replace(input) : clear end
558
+ def initialize(input = nil)
559
+ @set_data = new_set_data
560
+ @string = nil
561
+ replace(input) unless input.nil?
562
+ end
535
563
 
536
564
  # Removes all elements and returns self.
537
565
  def clear
538
- modifying! # redundant check, to normalize the error message for JRuby
539
- @tuples, @string = [], nil
566
+ modifying! # redundant check (normalizes the error message for JRuby)
567
+ set_data.clear
568
+ @string = nil
540
569
  self
541
570
  end
542
571
 
@@ -549,7 +578,7 @@ module Net
549
578
  case other
550
579
  when SequenceSet then
551
580
  modifying! # short circuit before doing any work
552
- @tuples = other.deep_copy_tuples
581
+ @set_data = other.dup_set_data
553
582
  @string = other.instance_variable_get(:@string)
554
583
  when String then self.string = other
555
584
  else clear; merge other
@@ -580,7 +609,7 @@ module Net
580
609
  # the set is updated the string will be normalized.
581
610
  #
582
611
  # Related: #valid_string, #normalized_string, #to_s, #inspect
583
- def string; @string ||= normalized_string if valid? end
612
+ def string; @string || normalized_string if valid? end
584
613
 
585
614
  # Returns an array with #normalized_string when valid and an empty array
586
615
  # otherwise.
@@ -599,13 +628,18 @@ module Net
599
628
  clear
600
629
  elsif (str = String.try_convert(input))
601
630
  modifying! # short-circuit before parsing the string
602
- tuples = str_to_tuples str
603
- @tuples, @string = [], -str
604
- tuples_add tuples
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
605
639
  else
606
640
  raise ArgumentError, "expected a string or nil, got #{input.class}"
607
641
  end
608
- str
642
+ input
609
643
  end
610
644
 
611
645
  # Returns the \IMAP +sequence-set+ string representation, or an empty
@@ -618,8 +652,7 @@ module Net
618
652
  # Freezes and returns the set. A frozen SequenceSet is Ractor-safe.
619
653
  def freeze
620
654
  return self if frozen?
621
- string
622
- @tuples.each(&:freeze).freeze
655
+ freeze_set_data
623
656
  super
624
657
  end
625
658
 
@@ -641,7 +674,7 @@ module Net
641
674
  # Related: #eql?, #normalize
642
675
  def ==(other)
643
676
  self.class == other.class &&
644
- (to_s == other.to_s || tuples == other.tuples)
677
+ (to_s == other.to_s || set_data == other.set_data)
645
678
  end
646
679
 
647
680
  # :call-seq: eql?(other) -> true or false
@@ -682,7 +715,7 @@ module Net
682
715
  # object that would be accepted by ::new.
683
716
  #
684
717
  # Related: #===, #include?, #include_star?, #intersect?
685
- def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
718
+ def cover?(other) import_runs(other).none? { !include_run?(_1) } end
686
719
 
687
720
  # Returns +true+ when a given number or range is in +self+, and +false+
688
721
  # otherwise. Returns +nil+ when +number+ isn't a valid SequenceSet
@@ -709,14 +742,14 @@ module Net
709
742
  #
710
743
  # Related: #include_star?, #cover?, #===, #intersect?
711
744
  def include?(element)
712
- tuple = input_to_tuple element rescue nil
713
- !!include_tuple?(tuple) if tuple
745
+ run = import_run element rescue nil
746
+ !!include_run?(run) if run
714
747
  end
715
748
 
716
749
  alias member? include?
717
750
 
718
751
  # Returns +true+ when the set contains <tt>*</tt>.
719
- def include_star?; @tuples.last&.last == STAR_INT end
752
+ def include_star?; max_num == STAR_INT end
720
753
 
721
754
  # Returns +true+ if the set and a given object have any common elements,
722
755
  # +false+ otherwise.
@@ -726,7 +759,7 @@ module Net
726
759
  #
727
760
  # Related: #intersection, #disjoint?, #cover?, #include?
728
761
  def intersect?(other)
729
- valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
762
+ valid? && import_runs(other).any? { intersect_run? _1 }
730
763
  end
731
764
  alias overlap? intersect?
732
765
 
@@ -738,7 +771,7 @@ module Net
738
771
  #
739
772
  # Related: #intersection, #intersect?
740
773
  def disjoint?(other)
741
- empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
774
+ empty? || import_runs(other).none? { intersect_run? _1 }
742
775
  end
743
776
 
744
777
  # :call-seq:
@@ -755,8 +788,12 @@ module Net
755
788
  # Related: #min, #minmax, #slice
756
789
  def max(count = nil, star: :*)
757
790
  if count
758
- slice(-[count, size].min..) || remain_frozen_empty
759
- elsif (val = @tuples.last&.last)
791
+ if cardinality <= count
792
+ frozen? ? self : dup
793
+ else
794
+ slice(-count..) || remain_frozen_empty
795
+ end
796
+ elsif (val = max_num)
760
797
  val == STAR_INT ? star : val
761
798
  end
762
799
  end
@@ -776,7 +813,7 @@ module Net
776
813
  def min(count = nil, star: :*)
777
814
  if count
778
815
  slice(0...count) || remain_frozen_empty
779
- elsif (val = @tuples.first&.first)
816
+ elsif (val = min_num)
780
817
  val != STAR_INT ? val : star
781
818
  end
782
819
  end
@@ -794,10 +831,10 @@ module Net
794
831
  def valid?; !empty? end
795
832
 
796
833
  # Returns true if the set contains no elements
797
- def empty?; @tuples.empty? end
834
+ def empty?; runs.empty? end
798
835
 
799
836
  # Returns true if the set contains every possible element.
800
- def full?; @tuples == [[1, STAR_INT]] end
837
+ def full?; set_data == FULL_SET_DATA end
801
838
 
802
839
  # :call-seq:
803
840
  # self + other -> sequence set
@@ -873,9 +910,7 @@ module Net
873
910
  # * <tt>lhs - (lhs - rhs)</tt>
874
911
  # * <tt>lhs - (lhs ^ rhs)</tt>
875
912
  # * <tt>lhs ^ (lhs - rhs)</tt>
876
- def &(other)
877
- remain_frozen dup.subtract SequenceSet.new(other).complement!
878
- end
913
+ def &(other) remain_frozen dup.intersect! other end
879
914
  alias intersection :&
880
915
 
881
916
  # :call-seq:
@@ -900,7 +935,7 @@ module Net
900
935
  # * <tt>(lhs | rhs) - (lhs & rhs)</tt>
901
936
  # * <tt>(lhs - rhs) | (rhs - lhs)</tt>
902
937
  # * <tt>(lhs ^ other) ^ (other ^ rhs)</tt>
903
- def ^(other) remain_frozen (dup | other).subtract(self & other) end
938
+ def ^(other) remain_frozen dup.xor! other end
904
939
  alias xor :^
905
940
 
906
941
  # :call-seq:
@@ -939,8 +974,8 @@ module Net
939
974
  #
940
975
  # Related: #add?, #merge, #union, #append
941
976
  def add(element)
942
- modifying! # short-circuit before input_to_tuple
943
- tuple_add input_to_tuple element
977
+ modifying! # short-circuit before import_run
978
+ add_run import_run element
944
979
  normalize!
945
980
  end
946
981
  alias << add
@@ -950,16 +985,55 @@ module Net
950
985
  # Unlike #add, #merge, or #union, the new value is appended to #string.
951
986
  # This may result in a #string which has duplicates or is out-of-order.
952
987
  #
953
- # See SequenceSet@Ordered+and+Normalized+sets.
988
+ # set = Net::IMAP::SequenceSet.new
989
+ # set.append(1..2) # => Net::IMAP::SequenceSet("1:2")
990
+ # set.append(5) # => Net::IMAP::SequenceSet("1:2,5")
991
+ # set.append(4) # => Net::IMAP::SequenceSet("1:2,5,4")
992
+ # set.append(3) # => Net::IMAP::SequenceSet("1:2,5,4,3")
993
+ # set.append(2) # => Net::IMAP::SequenceSet("1:2,5,4,3,2")
994
+ #
995
+ # If +entry+ is a string, it will be converted into normal form.
996
+ #
997
+ # set = Net::IMAP::SequenceSet("4:5,1:2")
998
+ # set.append("6:6") # => Net::IMAP::SequenceSet("4:5,1:2,6")
999
+ # set.append("9:8") # => Net::IMAP::SequenceSet("4:5,1:2,6,8:9")
1000
+ #
1001
+ # If +entry+ adjacently follows the last entry, they will coalesced:
1002
+ # set = Net::IMAP::SequenceSet.new("2,1,9:10")
1003
+ # set.append(11..12) # => Net::IMAP::SequenceSet("2,1,9:12")
1004
+ #
1005
+ # Non-normalized sets store the string <em>in addition to</em> an internal
1006
+ # normalized uint32 set representation. This can more than double memory
1007
+ # usage, so large sets should avoid using #append unless preserving order
1008
+ # is required. See SequenceSet@Ordered+and+Normalized+sets.
954
1009
  #
955
1010
  # Related: #add, #merge, #union
956
1011
  def append(entry)
957
- modifying! # short-circuit before input_to_tuple
958
- tuple = input_to_tuple entry
959
- entry = tuple_to_str tuple
960
- string unless empty? # write @string before tuple_add
961
- tuple_add tuple
962
- @string = -(@string ? "#{@string},#{entry}" : entry)
1012
+ modifying! # short-circuit before import_minmax
1013
+ minmax = import_minmax entry
1014
+ adj = minmax.first - 1
1015
+ if @string.nil? && (runs.empty? || max_num <= adj)
1016
+ # append to elements or coalesce with last element
1017
+ add_minmax minmax
1018
+ return self
1019
+ elsif @string.nil?
1020
+ # generate string for out-of-order append
1021
+ head, comma = normalized_string, ","
1022
+ else
1023
+ # @string already exists... maybe coalesce with last entry
1024
+ head, comma, last_entry = @string.rpartition(",")
1025
+ last_min, last_max = import_minmax last_entry
1026
+ if last_max == adj
1027
+ # coalesce with last entry
1028
+ minmax[0] = last_min
1029
+ else
1030
+ # append to existing string
1031
+ head, comma = @string, ","
1032
+ end
1033
+ end
1034
+ entry = export_minmax minmax
1035
+ add_minmax minmax
1036
+ @string = -"#{head}#{comma}#{entry}"
963
1037
  self
964
1038
  end
965
1039
 
@@ -985,8 +1059,8 @@ module Net
985
1059
  #
986
1060
  # Related: #delete?, #delete_at, #subtract, #difference
987
1061
  def delete(element)
988
- modifying! # short-circuit before input_to_tuple
989
- tuple_subtract input_to_tuple element
1062
+ modifying! # short-circuit before import_run
1063
+ subtract_run import_run element
990
1064
  normalize!
991
1065
  end
992
1066
 
@@ -1023,16 +1097,16 @@ module Net
1023
1097
  #
1024
1098
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
1025
1099
  def delete?(element)
1026
- modifying! # short-circuit before input_to_tuple
1027
- tuple = input_to_tuple element
1028
- if tuple.first == tuple.last
1029
- return unless include_tuple? tuple
1030
- tuple_subtract tuple
1100
+ modifying! # short-circuit before import_minmax
1101
+ minmax = import_minmax element
1102
+ if minmax.first == minmax.last
1103
+ return unless include_minmax? minmax
1104
+ subtract_minmax minmax
1031
1105
  normalize!
1032
- from_tuple_int tuple.first
1106
+ export_num minmax.first
1033
1107
  else
1034
1108
  copy = dup
1035
- tuple_subtract tuple
1109
+ subtract_minmax minmax
1036
1110
  normalize!
1037
1111
  copy if copy.subtract(self).valid?
1038
1112
  end
@@ -1069,8 +1143,8 @@ module Net
1069
1143
  deleted
1070
1144
  end
1071
1145
 
1072
- # Merges all of the elements that appear in any of the +sets+ into the
1073
- # set, and returns +self+.
1146
+ # In-place set #union. Merges all of the elements that appear in any of
1147
+ # the +sets+ into this set, and returns +self+.
1074
1148
  #
1075
1149
  # The +sets+ may be any objects that would be accepted by ::new.
1076
1150
  #
@@ -1078,19 +1152,20 @@ module Net
1078
1152
  #
1079
1153
  # Related: #add, #add?, #union
1080
1154
  def merge(*sets)
1081
- modifying! # short-circuit before input_to_tuples
1082
- tuples_add input_to_tuples sets
1155
+ modifying! # short-circuit before import_runs
1156
+ add_runs import_runs sets
1083
1157
  normalize!
1084
1158
  end
1085
1159
 
1086
- # Removes all of the elements that appear in any of the given +sets+ from
1087
- # the set, and returns +self+.
1160
+ # In-place set #difference. Removes all of the elements that appear in
1161
+ # any of the given +sets+ from this set, and returns +self+.
1088
1162
  #
1089
1163
  # The +sets+ may be any objects that would be accepted by ::new.
1090
1164
  #
1091
1165
  # Related: #difference
1092
1166
  def subtract(*sets)
1093
- tuples_subtract input_to_tuples sets
1167
+ modifying! # short-circuit before import_runs
1168
+ subtract_runs import_runs sets
1094
1169
  normalize!
1095
1170
  end
1096
1171
 
@@ -1182,7 +1257,7 @@ module Net
1182
1257
  # Related: #entries, #each_element
1183
1258
  def each_entry(&block) # :yields: integer or range or :*
1184
1259
  return to_enum(__method__) unless block_given?
1185
- each_entry_tuple do yield tuple_to_entry _1 end
1260
+ each_entry_run do yield export_run_entry _1 end
1186
1261
  end
1187
1262
 
1188
1263
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
@@ -1194,39 +1269,17 @@ module Net
1194
1269
  # Related: #elements, #each_entry
1195
1270
  def each_element # :yields: integer or range or :*
1196
1271
  return to_enum(__method__) unless block_given?
1197
- @tuples.each do yield tuple_to_entry _1 end
1198
- self
1199
- end
1200
-
1201
- private
1202
-
1203
- def each_entry_tuple(&block)
1204
- return to_enum(__method__) unless block_given?
1205
- if @string
1206
- @string.split(",") do block.call str_to_tuple _1 end
1207
- else
1208
- @tuples.each(&block)
1209
- end
1272
+ runs.each do yield export_run_entry _1 end
1210
1273
  self
1211
1274
  end
1212
1275
 
1213
- def tuple_to_entry((min, max))
1214
- if min == STAR_INT then :*
1215
- elsif max == STAR_INT then min..
1216
- elsif min == max then min
1217
- else min..max
1218
- end
1219
- end
1220
-
1221
- public
1222
-
1223
1276
  # Yields each range in #ranges to the block and returns self.
1224
1277
  # Returns an enumerator when called without a block.
1225
1278
  #
1226
1279
  # Related: #ranges
1227
1280
  def each_range # :yields: range
1228
1281
  return to_enum(__method__) unless block_given?
1229
- @tuples.each do |min, max|
1282
+ minmaxes.each do |min, max|
1230
1283
  if min == STAR_INT then yield :*..
1231
1284
  elsif max == STAR_INT then yield min..
1232
1285
  else yield min..max
@@ -1245,7 +1298,7 @@ module Net
1245
1298
  def each_number(&block) # :yields: integer
1246
1299
  return to_enum(__method__) unless block_given?
1247
1300
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
1248
- @tuples.each do each_number_in_tuple _1, _2, &block end
1301
+ minmaxes.each do each_number_in_minmax _1, _2, &block end
1249
1302
  self
1250
1303
  end
1251
1304
 
@@ -1259,16 +1312,7 @@ module Net
1259
1312
  def each_ordered_number(&block)
1260
1313
  return to_enum(__method__) unless block_given?
1261
1314
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
1262
- each_entry_tuple do each_number_in_tuple _1, _2, &block end
1263
- end
1264
-
1265
- private def each_number_in_tuple(min, max, &block)
1266
- if min == STAR_INT then yield :*
1267
- elsif min == max then yield min
1268
- elsif max != STAR_INT then (min..max).each(&block)
1269
- else
1270
- raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1271
- end
1315
+ each_entry_minmax do each_number_in_minmax _1, _2, &block end
1272
1316
  end
1273
1317
 
1274
1318
  # Returns a Set with all of the #numbers in the sequence set.
@@ -1280,35 +1324,103 @@ module Net
1280
1324
  # Related: #elements, #ranges, #numbers
1281
1325
  def to_set; Set.new(numbers) end
1282
1326
 
1283
- # Returns the count of #numbers in the set.
1327
+ # Returns the number of members in the set.
1328
+ #
1329
+ # Unlike #count, <tt>"*"</tt> is considered to be distinct from
1330
+ # <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1331
+ #
1332
+ # set = Net::IMAP::SequenceSet[1..10]
1333
+ # set.count #=> 10
1334
+ # set.cardinality #=> 10
1335
+ #
1336
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1337
+ # set.count #=> 1
1338
+ # set.cardinality #=> 2
1339
+ #
1340
+ # set = Net::IMAP::SequenceSet[1..]
1341
+ # set.count #=> 4294967295
1342
+ # set.cardinality #=> 4294967296
1343
+ #
1344
+ # Related: #count, #count_with_duplicates
1345
+ def cardinality = minmaxes.sum(runs.count) { _2 - _1 }
1346
+
1347
+ # Returns the count of distinct #numbers in the set.
1348
+ #
1349
+ # Unlike #cardinality, <tt>"*"</tt> is considered to be equal to
1350
+ # <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1351
+ #
1352
+ # set = Net::IMAP::SequenceSet[1..10]
1353
+ # set.count #=> 10
1354
+ # set.cardinality #=> 10
1355
+ #
1356
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1357
+ # set.count #=> 1
1358
+ # set.cardinality #=> 2
1284
1359
  #
1285
- # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1286
- # unsigned integer value).
1360
+ # set = Net::IMAP::SequenceSet[1..]
1361
+ # set.count #=> 4294967295
1362
+ # set.cardinality #=> 4294967296
1287
1363
  #
1288
- # Related: #count_with_duplicates
1364
+ # Related: #cardinality, #count_with_duplicates
1289
1365
  def count
1290
- @tuples.sum(@tuples.count) { _2 - _1 } +
1291
- (include_star? && include?(UINT32_MAX) ? -1 : 0)
1366
+ cardinality + (include_star? && include?(UINT32_MAX) ? -1 : 0)
1292
1367
  end
1293
1368
 
1294
- alias size count
1295
-
1296
1369
  # Returns the count of numbers in the ordered #entries, including any
1297
1370
  # repeated numbers.
1298
1371
  #
1299
- # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1300
- # unsigned integer value).
1301
- #
1302
- # When #string is normalized, this behaves the same as #count.
1303
- #
1304
- # Related: #entries, #count_duplicates, #has_duplicates?
1372
+ # When #string is normalized, this returns the same as #count. Like
1373
+ # #count, <tt>"*"</tt> is considered to be equal to <tt>2³² - 1</tt> (the
1374
+ # maximum 32-bit unsigned integer value).
1375
+ #
1376
+ # In a range, <tt>"*"</tt> is _not_ considered a duplicate:
1377
+ # set = Net::IMAP::SequenceSet["4294967295:*"]
1378
+ # set.count_with_duplicates #=> 1
1379
+ # set.size #=> 2
1380
+ # set.count #=> 1
1381
+ # set.cardinality #=> 2
1382
+ #
1383
+ # In a separate entry, <tt>"*"</tt> _is_ considered a duplicate:
1384
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1385
+ # set.count_with_duplicates #=> 2
1386
+ # set.size #=> 2
1387
+ # set.count #=> 1
1388
+ # set.cardinality #=> 2
1389
+ #
1390
+ # Related: #count, #cardinality, #size, #count_duplicates,
1391
+ # #has_duplicates?, #entries
1305
1392
  def count_with_duplicates
1306
1393
  return count unless @string
1307
- each_entry_tuple.sum {|min, max|
1394
+ each_entry_minmax.sum {|min, max|
1308
1395
  max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
1309
1396
  }
1310
1397
  end
1311
1398
 
1399
+ # Returns the combined size of the ordered #entries, including any
1400
+ # repeated numbers.
1401
+ #
1402
+ # When #string is normalized, this returns the same as #cardinality.
1403
+ # Like #cardinality, <tt>"*"</tt> is considered to be be distinct from
1404
+ # <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
1405
+ #
1406
+ # set = Net::IMAP::SequenceSet["4294967295:*"]
1407
+ # set.size #=> 2
1408
+ # set.count_with_duplicates #=> 1
1409
+ # set.count #=> 1
1410
+ # set.cardinality #=> 2
1411
+ #
1412
+ # set = Net::IMAP::SequenceSet["4294967295,*"]
1413
+ # set.size #=> 2
1414
+ # set.count_with_duplicates #=> 2
1415
+ # set.count #=> 1
1416
+ # set.cardinality #=> 2
1417
+ #
1418
+ # Related: #cardinality, #count_with_duplicates, #count, #entries
1419
+ def size
1420
+ return cardinality unless @string
1421
+ each_entry_minmax.sum {|min, max| max - min + 1 }
1422
+ end
1423
+
1312
1424
  # Returns the count of repeated numbers in the ordered #entries, the
1313
1425
  # difference between #count_with_duplicates and #count.
1314
1426
  #
@@ -1326,7 +1438,7 @@ module Net
1326
1438
  #
1327
1439
  # Always returns +false+ when #string is normalized.
1328
1440
  #
1329
- # Related: #entries, #count_with_duplicates, #count_duplicates?
1441
+ # Related: #entries, #count_with_duplicates, #count_duplicates
1330
1442
  def has_duplicates?
1331
1443
  return false unless @string
1332
1444
  count_with_duplicates != count
@@ -1337,10 +1449,10 @@ module Net
1337
1449
  #
1338
1450
  # Related: #[], #at, #find_ordered_index
1339
1451
  def find_index(number)
1340
- number = to_tuple_int number
1341
- each_tuple_with_index(@tuples) do |min, max, idx_min|
1452
+ number = import_num number
1453
+ each_minmax_with_index(minmaxes) do |min, max, idx_min|
1342
1454
  number < min and return nil
1343
- number <= max and return from_tuple_int(idx_min + (number - min))
1455
+ number <= max and return export_num(idx_min + (number - min))
1344
1456
  end
1345
1457
  nil
1346
1458
  end
@@ -1350,38 +1462,15 @@ module Net
1350
1462
  #
1351
1463
  # Related: #find_index
1352
1464
  def find_ordered_index(number)
1353
- number = to_tuple_int number
1354
- each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1465
+ number = import_num number
1466
+ each_minmax_with_index(each_entry_minmax) do |min, max, idx_min|
1355
1467
  if min <= number && number <= max
1356
- return from_tuple_int(idx_min + (number - min))
1468
+ return export_num(idx_min + (number - min))
1357
1469
  end
1358
1470
  end
1359
1471
  nil
1360
1472
  end
1361
1473
 
1362
- private
1363
-
1364
- def each_tuple_with_index(tuples)
1365
- idx_min = 0
1366
- tuples.each do |min, max|
1367
- idx_max = idx_min + (max - min)
1368
- yield min, max, idx_min, idx_max
1369
- idx_min = idx_max + 1
1370
- end
1371
- idx_min
1372
- end
1373
-
1374
- def reverse_each_tuple_with_index(tuples)
1375
- idx_max = -1
1376
- tuples.reverse_each do |min, max|
1377
- yield min, max, (idx_min = idx_max - (max - min)), idx_max
1378
- idx_max = idx_min - 1
1379
- end
1380
- idx_max
1381
- end
1382
-
1383
- public
1384
-
1385
1474
  # :call-seq: at(index) -> integer or nil
1386
1475
  #
1387
1476
  # Returns the number at the given +index+ in the sorted set, without
@@ -1392,7 +1481,7 @@ module Net
1392
1481
  #
1393
1482
  # Related: #[], #slice, #ordered_at
1394
1483
  def at(index)
1395
- lookup_number_by_tuple_index(tuples, index)
1484
+ seek_number_in_minmaxes(minmaxes, index)
1396
1485
  end
1397
1486
 
1398
1487
  # :call-seq: ordered_at(index) -> integer or nil
@@ -1405,21 +1494,7 @@ module Net
1405
1494
  #
1406
1495
  # Related: #[], #slice, #ordered_at
1407
1496
  def ordered_at(index)
1408
- lookup_number_by_tuple_index(each_entry_tuple, index)
1409
- end
1410
-
1411
- private def lookup_number_by_tuple_index(tuples, index)
1412
- index = Integer(index.to_int)
1413
- if index.negative?
1414
- reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1415
- idx_min <= index and return from_tuple_int(min + (index - idx_min))
1416
- end
1417
- else
1418
- each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1419
- index <= idx_max and return from_tuple_int(min + (index - idx_min))
1420
- end
1421
- end
1422
- nil
1497
+ seek_number_in_minmaxes(each_entry_minmax, index)
1423
1498
  end
1424
1499
 
1425
1500
  # :call-seq:
@@ -1470,37 +1545,6 @@ module Net
1470
1545
 
1471
1546
  alias slice :[]
1472
1547
 
1473
- private
1474
-
1475
- def slice_length(start, length)
1476
- start = Integer(start.to_int)
1477
- length = Integer(length.to_int)
1478
- raise ArgumentError, "length must be positive" unless length.positive?
1479
- last = start + length - 1 unless start.negative? && start.abs <= length
1480
- slice_range(start..last)
1481
- end
1482
-
1483
- def slice_range(range)
1484
- first = range.begin || 0
1485
- last = range.end || -1
1486
- if range.exclude_end?
1487
- return remain_frozen_empty if last.zero?
1488
- last -= 1 if range.end && last != STAR_INT
1489
- end
1490
- if (first * last).positive? && last < first
1491
- remain_frozen_empty
1492
- elsif (min = at(first))
1493
- max = at(last)
1494
- max = :* if max.nil?
1495
- if max == :* then self & (min..)
1496
- elsif min <= max then self & (min..max)
1497
- else remain_frozen_empty
1498
- end
1499
- end
1500
- end
1501
-
1502
- public
1503
-
1504
1548
  # Returns a copy of +self+ which only contains the numbers above +num+.
1505
1549
  #
1506
1550
  # Net::IMAP::SequenceSet["5,10:22,50"].above(10) # to_s => "11:22,50"
@@ -1572,7 +1616,7 @@ module Net
1572
1616
  #
1573
1617
  # Related: #limit!
1574
1618
  def limit(max:)
1575
- max = to_tuple_int(max)
1619
+ max = import_num(max)
1576
1620
  if empty? then self.class.empty
1577
1621
  elsif !include_star? && max < min then self.class.empty
1578
1622
  elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze
@@ -1585,53 +1629,134 @@ module Net
1585
1629
  #
1586
1630
  # Related: #limit
1587
1631
  def limit!(max:)
1588
- modifying! # short-circuit, and normalize the error message for JRuby
1632
+ modifying! # short-circuit before querying
1589
1633
  star = include_star?
1590
- max = to_tuple_int(max)
1591
- tuple_subtract [max + 1, STAR_INT]
1592
- tuple_add [max, max ] if star
1634
+ max = import_num(max)
1635
+ subtract_minmax [max + 1, STAR_INT]
1636
+ add_minmax [max, max ] if star
1593
1637
  normalize!
1594
1638
  end
1595
1639
 
1596
1640
  # :call-seq: complement! -> self
1597
1641
  #
1598
- # Converts the SequenceSet to its own #complement. It will contain all
1599
- # possible values _except_ for those currently in the set.
1642
+ # In-place set #complement. Replaces the contents of this set with its
1643
+ # own #complement. It will contain all possible values _except_ for those
1644
+ # currently in the set.
1600
1645
  #
1601
1646
  # Related: #complement
1602
1647
  def complement!
1603
- modifying! # short-circuit, and normalize the error message for JRuby
1648
+ modifying! # short-circuit before querying
1604
1649
  return replace(self.class.full) if empty?
1605
1650
  return clear if full?
1606
- flat = @tuples.flat_map { [_1 - 1, _2 + 1] }
1651
+ flat = minmaxes.flat_map { [_1 - 1, _2 + 1] }
1607
1652
  if flat.first < 1 then flat.shift else flat.unshift 1 end
1608
1653
  if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end
1609
- @tuples = flat.each_slice(2).to_a
1654
+ replace_minmaxes flat.each_slice(2).to_a
1610
1655
  normalize!
1611
1656
  end
1612
1657
 
1613
- # Returns a new SequenceSet with a normalized string representation.
1658
+ # In-place set #intersection. Removes any elements that are missing from
1659
+ # +other+ from this set, keeping only the #intersection, and returns
1660
+ # +self+.
1661
+ #
1662
+ # +other+ can be any object that would be accepted by ::new.
1663
+ #
1664
+ # set = Net::IMAP::SequenceSet.new(1..5)
1665
+ # set.intersect! [2, 4, 6]
1666
+ # set #=> Net::IMAP::SequenceSet("2,4")
1667
+ #
1668
+ # Related: #intersection, #intersect?
1669
+ def intersect!(other)
1670
+ modifying! # short-circuit before processing input
1671
+ subtract SequenceSet.new(other).complement!
1672
+ end
1673
+
1674
+ # In-place set #xor. Adds any numbers in +other+ that are missing from
1675
+ # this set, removes any numbers in +other+ that are already in this set,
1676
+ # and returns +self+.
1677
+ #
1678
+ # +other+ can be any object that would be accepted by ::new.
1679
+ #
1680
+ # set = Net::IMAP::SequenceSet.new(1..5)
1681
+ # set.xor! [2, 4, 6]
1682
+ # set #=> Net::IMAP::SequenceSet["1,3,5:6"]
1683
+ #
1684
+ # Related: #xor, #merge, #subtract
1685
+ def xor!(other)
1686
+ modifying! # short-circuit before processing input
1687
+ other = SequenceSet.new(other)
1688
+ copy = dup
1689
+ merge(other).subtract(other.subtract(copy.complement!))
1690
+ end
1691
+
1692
+ # Returns whether #string is fully normalized: entries have been sorted,
1693
+ # deduplicated, and coalesced, and all entries are in normal form. See
1694
+ # SequenceSet@Ordered+and+Normalized+sets.
1695
+ #
1696
+ # Net::IMAP::SequenceSet["1,3,5"].normalized? #=> true
1697
+ # Net::IMAP::SequenceSet["20:30"].normalized? #=> true
1698
+ #
1699
+ # Net::IMAP::SequenceSet["3,5,1"].normalized? #=> false, not sorted
1700
+ # Net::IMAP::SequenceSet["1,2,3"].normalized? #=> false, not coalesced
1701
+ # Net::IMAP::SequenceSet["1:5,2"].normalized? #=> false, repeated number
1702
+ #
1703
+ # Net::IMAP::SequenceSet["1:1"].normalized? #=> false, number as range
1704
+ # Net::IMAP::SequenceSet["5:1"].normalized? #=> false, backwards range
1705
+ #
1706
+ # Returns +true+ if (and only if) #string is equal to #normalized_string:
1707
+ # seqset = Net::IMAP::SequenceSet["1:3,5"]
1708
+ # seqset.string #=> "1:3,5"
1709
+ # seqset.normalized_string #=> "1:3,5"
1710
+ # seqset.entries #=> [1..3, 5]
1711
+ # seqset.elements #=> [1..3, 5]
1712
+ # seqset.normalized? #=> true
1713
+ #
1714
+ # seqset = Net::IMAP::SequenceSet["3,1,2"]
1715
+ # seqset.string #=> "3,1,2"
1716
+ # seqset.normalized_string #=> "1:3"
1717
+ # seqset.entries #=> [3, 1, 2]
1718
+ # seqset.elements #=> [1..3]
1719
+ # seqset.normalized? #=> false
1720
+ #
1721
+ # Can return +false+ even when #entries and #elements are the same:
1722
+ # seqset = Net::IMAP::SequenceSet["5:1"]
1723
+ # seqset.string #=> "5:1"
1724
+ # seqset.normalized_string #=> "1:5"
1725
+ # seqset.entries #=> [1..5]
1726
+ # seqset.elements #=> [1..5]
1727
+ # seqset.normalized? #=> false
1728
+ #
1729
+ # Note that empty sets are normalized, even though they are not #valid?:
1730
+ # seqset = Net::IMAP::SequenceSet.empty
1731
+ # seqset.normalized? #=> true
1732
+ # seqset.valid? #=> false
1733
+ #
1734
+ # Related: #normalize, #normalize!, #normalized_string
1735
+ def normalized?
1736
+ @string.nil? || normal_string?(@string)
1737
+ end
1738
+
1739
+ # Returns a SequenceSet with a normalized string representation: entries
1740
+ # have been sorted, deduplicated, and coalesced, and all entries
1741
+ # are in normal form. Returns +self+ for frozen normalized sets, and a
1742
+ # normalized duplicate otherwise.
1614
1743
  #
1615
- # The returned set's #string is sorted and deduplicated. Adjacent or
1616
- # overlapping elements will be merged into a single larger range.
1617
1744
  # See SequenceSet@Ordered+and+Normalized+sets.
1618
1745
  #
1619
1746
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
1620
1747
  # #=> Net::IMAP::SequenceSet["1:7,9:11"]
1621
1748
  #
1622
- # Related: #normalize!, #normalized_string
1749
+ # Related: #normalize!, #normalized_string, #normalized?
1623
1750
  def normalize
1624
- str = normalized_string
1625
- return self if frozen? && str == string
1626
- remain_frozen dup.instance_exec { @string = str&.-@; self }
1751
+ frozen? && normalized? ? self : remain_frozen(dup.normalize!)
1627
1752
  end
1628
1753
 
1629
1754
  # Resets #string to be sorted, deduplicated, and coalesced. Returns
1630
1755
  # +self+. See SequenceSet@Ordered+and+Normalized+sets.
1631
1756
  #
1632
- # Related: #normalize, #normalized_string
1757
+ # Related: #normalize, #normalized_string, #normalized?
1633
1758
  def normalize!
1634
- modifying! # redundant check, to normalize the error message for JRuby
1759
+ modifying! # redundant check (normalizes the error message for JRuby)
1635
1760
  @string = nil
1636
1761
  self
1637
1762
  end
@@ -1645,9 +1770,9 @@ module Net
1645
1770
  #
1646
1771
  # Returns +nil+ when the set is empty.
1647
1772
  #
1648
- # Related: #normalize!, #normalize, #string, #to_s
1773
+ # Related: #normalize!, #normalize, #string, #to_s, #normalized?
1649
1774
  def normalized_string
1650
- @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
1775
+ export_runs(runs) unless runs.empty?
1651
1776
  end
1652
1777
 
1653
1778
  # Returns an inspection string for the SequenceSet.
@@ -1684,8 +1809,8 @@ module Net
1684
1809
  head = @string[INSPECT_ABRIDGED_HEAD_RE]
1685
1810
  tail = @string[INSPECT_ABRIDGED_TAIL_RE]
1686
1811
  else
1687
- head = export_string_entries(@tuples.first(INSPECT_TRUNCATE_LEN)) + ","
1688
- tail = "," + export_string_entries(@tuples.last(INSPECT_TRUNCATE_LEN))
1812
+ head = export_runs(runs.first(INSPECT_TRUNCATE_LEN)) + ","
1813
+ tail = "," + export_runs(runs.last(INSPECT_TRUNCATE_LEN))
1689
1814
  end
1690
1815
  '#<%s %d entries "%s...(%d entries omitted)...%s"%s>' % [
1691
1816
  self.class, count,
@@ -1695,10 +1820,6 @@ module Net
1695
1820
  end
1696
1821
  end
1697
1822
 
1698
- private def count_entries
1699
- @string ? @string.count(",") + 1 : @tuples.count
1700
- end
1701
-
1702
1823
  ##
1703
1824
  # :method: to_sequence_set
1704
1825
  # :call-seq: to_sequence_set -> self
@@ -1729,15 +1850,17 @@ module Net
1729
1850
 
1730
1851
  # For YAML deserialization
1731
1852
  def init_with(coder) # :nodoc:
1732
- @tuples = []
1853
+ @set_data = new_set_data
1733
1854
  self.string = coder['string']
1734
1855
  end
1735
1856
 
1857
+ # :stopdoc:
1736
1858
  protected
1737
1859
 
1738
- attr_reader :tuples # :nodoc:
1860
+ attr_reader :set_data
1739
1861
 
1740
- def deep_copy_tuples; @tuples.map { _1.dup } end # :nodoc:
1862
+ alias runs set_data
1863
+ alias minmaxes runs
1741
1864
 
1742
1865
  private
1743
1866
 
@@ -1746,38 +1869,42 @@ module Net
1746
1869
 
1747
1870
  # frozen clones are shallow copied
1748
1871
  def initialize_clone(other)
1749
- @tuples = other.deep_copy_tuples unless other.frozen?
1872
+ @set_data = other.dup_set_data unless other.frozen?
1750
1873
  super
1751
1874
  end
1752
1875
 
1753
1876
  def initialize_dup(other)
1754
- @tuples = other.deep_copy_tuples
1877
+ @set_data = other.dup_set_data
1755
1878
  super
1756
1879
  end
1757
1880
 
1758
- def input_to_tuple(entry)
1759
- entry = input_try_convert entry
1881
+ ######################################################################{{{2
1882
+ # Import methods
1883
+
1884
+ def import_minmax(input)
1885
+ entry = input_try_convert input
1760
1886
  case entry
1761
- when *STARS, Integer then [int = to_tuple_int(entry), int]
1762
- when Range then range_to_tuple(entry)
1763
- when String then str_to_tuple(entry)
1887
+ when *STARS, Integer then [int = import_num(entry), int]
1888
+ when Range then import_range_minmax(entry)
1889
+ when String then parse_minmax(entry)
1764
1890
  else
1765
- raise DataFormatError, "expected number or range, got %p" % [entry]
1891
+ raise DataFormatError, "expected number or range, got %p" % [input]
1766
1892
  end
1767
1893
  end
1894
+ alias import_run import_minmax
1768
1895
 
1769
- def input_to_tuples(set)
1770
- set = input_try_convert set
1896
+ def import_runs(input)
1897
+ set = input_try_convert input
1771
1898
  case set
1772
- when *STARS, Integer, Range then [input_to_tuple(set)]
1773
- when String then str_to_tuples set
1774
- when SequenceSet then set.tuples
1775
- when Set then set.map { [to_tuple_int(_1)] * 2 }
1776
- when Array then set.flat_map { input_to_tuples _1 }
1899
+ when *STARS, Integer, Range then [import_run(set)]
1900
+ when String then parse_runs set
1901
+ when SequenceSet then set.runs
1902
+ when Set then set.map { [import_num(_1)] * 2 }
1903
+ when Array then set.flat_map { import_runs _1 }
1777
1904
  when nil then []
1778
1905
  else
1779
1906
  raise DataFormatError, "expected nz-number, range, '*', Set, Array; " \
1780
- "got %p" % [set]
1907
+ "got %p" % [input]
1781
1908
  end
1782
1909
  end
1783
1910
 
@@ -1790,9 +1917,9 @@ module Net
1790
1917
  input
1791
1918
  end
1792
1919
 
1793
- def range_to_tuple(range)
1794
- first = to_tuple_int(range.begin || 1)
1795
- last = to_tuple_int(range.end || :*)
1920
+ def import_range_minmax(range)
1921
+ first = import_num(range.begin || 1)
1922
+ last = import_num(range.end || :*)
1796
1923
  last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1797
1924
  unless first <= last
1798
1925
  raise DataFormatError, "invalid range for sequence-set: %p" % [range]
@@ -1800,71 +1927,260 @@ module Net
1800
1927
  [first, last]
1801
1928
  end
1802
1929
 
1803
- def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
1804
- def from_tuple_int(num) num == STAR_INT ? :* : num end
1930
+ def import_num(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
1931
+ def nz_number(num) = NumValidator.coerce_nz_number(num)
1932
+
1933
+ ######################################################################{{{2
1934
+ # Export methods
1935
+
1936
+ def export_num(num) num == STAR_INT ? :* : num end
1937
+
1938
+ def export_minmaxes(minmaxes)
1939
+ -minmaxes.map { export_minmax _1 }.join(",")
1940
+ end
1941
+
1942
+ def export_minmax(minmax) minmax.uniq.map { export_num _1 }.join(":") end
1943
+
1944
+ alias export_runs export_minmaxes
1945
+ alias export_run export_minmax
1946
+
1947
+ def export_minmax_entry((min, max))
1948
+ if min == STAR_INT then :*
1949
+ elsif max == STAR_INT then min..
1950
+ elsif min == max then min
1951
+ else min..max
1952
+ end
1953
+ end
1954
+ alias export_run_entry export_minmax_entry
1805
1955
 
1806
- def export_string_entries(entries)
1807
- -entries.map { tuple_to_str _1 }.join(",")
1956
+ def each_number_in_minmax(min, max, &block)
1957
+ if min == STAR_INT then yield :*
1958
+ elsif min == max then yield min
1959
+ elsif max != STAR_INT then (min..max).each(&block)
1960
+ else
1961
+ raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1962
+ end
1808
1963
  end
1809
1964
 
1810
- def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
1811
- def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
1812
- def str_to_tuple(str)
1965
+ ######################################################################{{{2
1966
+ # Parse methods
1967
+
1968
+ def parse_runs(str) str.split(",", -1).map! { parse_run _1 } end
1969
+ def parse_minmax(str) parse_entry(str).minmax end
1970
+ alias parse_run parse_minmax
1971
+
1972
+ def parse_entry(str)
1813
1973
  raise DataFormatError, "invalid sequence set string" if str.empty?
1814
- str.split(":", 2).map! { to_tuple_int _1 }.minmax
1974
+ str.split(":", 2).map! { import_num _1 }
1975
+ end
1976
+
1977
+ # yields validated but unsorted [num] or [num, num]
1978
+ def each_parsed_entry(str)
1979
+ return to_enum(__method__, str) unless block_given?
1980
+ str&.split(",", -1) do |entry| yield parse_entry(entry) end
1981
+ end
1982
+
1983
+ def normal_string?(str) normalized_entries? each_parsed_entry str end
1984
+
1985
+ def normalized_entries?(entries)
1986
+ max = nil
1987
+ entries.each do |first, last|
1988
+ return false if last && last <= first # 1:1 or 2:1
1989
+ return false if max && first <= max + 1 # 2,1 or 1,1 or 1,2
1990
+ max = last || first
1991
+ end
1992
+ true
1993
+ end
1994
+
1995
+ ######################################################################{{{2
1996
+ # Ordered entry methods
1997
+
1998
+ def count_entries
1999
+ @string ? @string.count(",") + 1 : runs.count
2000
+ end
2001
+
2002
+ def each_entry_minmax(&block)
2003
+ return to_enum(__method__) unless block_given?
2004
+ if @string
2005
+ @string.split(",") do block.call parse_minmax _1 end
2006
+ else
2007
+ minmaxes.each(&block)
2008
+ end
2009
+ self
1815
2010
  end
2011
+ alias each_entry_run each_entry_minmax
2012
+
2013
+ ######################################################################{{{2
2014
+ # Search methods
1816
2015
 
1817
- def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end
2016
+ def include_minmax?((min, max)) bsearch_range(min)&.cover?(min..max) end
1818
2017
 
1819
- def intersect_tuple?((min, max))
1820
- range = range_gte_to(min) and
2018
+ def intersect_minmax?((min, max))
2019
+ range = bsearch_range(min) and
1821
2020
  range.include?(min) || range.include?(max) || (min..max).cover?(range)
1822
2021
  end
1823
2022
 
2023
+ alias include_run? include_minmax?
2024
+ alias intersect_run? intersect_minmax?
2025
+
2026
+ def bsearch_index(num) = minmaxes.bsearch_index { _2 >= num }
2027
+ def bsearch_minmax(num) = minmaxes.bsearch { _2 >= num }
2028
+ def bsearch_range(num) = (min, max = bsearch_minmax(num)) && (min..max)
2029
+
2030
+ ######################################################################{{{2
2031
+ # Number indexing methods
2032
+
2033
+ def seek_number_in_minmaxes(minmaxes, index)
2034
+ index = Integer(index.to_int)
2035
+ if index.negative?
2036
+ reverse_each_minmax_with_index(minmaxes) do |min, max, idx_min, idx_max|
2037
+ idx_min <= index and return export_num(min + (index - idx_min))
2038
+ end
2039
+ else
2040
+ each_minmax_with_index(minmaxes) do |min, _, idx_min, idx_max|
2041
+ index <= idx_max and return export_num(min + (index - idx_min))
2042
+ end
2043
+ end
2044
+ nil
2045
+ end
2046
+
2047
+ def each_minmax_with_index(minmaxes)
2048
+ idx_min = 0
2049
+ minmaxes.each do |min, max|
2050
+ idx_max = idx_min + (max - min)
2051
+ yield min, max, idx_min, idx_max
2052
+ idx_min = idx_max + 1
2053
+ end
2054
+ idx_min
2055
+ end
2056
+
2057
+ def reverse_each_minmax_with_index(minmaxes)
2058
+ idx_max = -1
2059
+ minmaxes.reverse_each do |min, max|
2060
+ yield min, max, (idx_min = idx_max - (max - min)), idx_max
2061
+ idx_max = idx_min - 1
2062
+ end
2063
+ idx_max
2064
+ end
2065
+
2066
+ def slice_length(start, length)
2067
+ start = Integer(start.to_int)
2068
+ length = Integer(length.to_int)
2069
+ raise ArgumentError, "length must be positive" unless length.positive?
2070
+ last = start + length - 1 unless start.negative? && start.abs <= length
2071
+ slice_range(start..last)
2072
+ end
2073
+
2074
+ def slice_range(range)
2075
+ first = range.begin || 0
2076
+ last = range.end || -1
2077
+ if range.exclude_end?
2078
+ return remain_frozen_empty if last.zero?
2079
+ last -= 1 if range.end && last != STAR_INT
2080
+ end
2081
+ if (first * last).positive? && last < first
2082
+ remain_frozen_empty
2083
+ elsif (min = at(first))
2084
+ max = at(last)
2085
+ max = :* if max.nil?
2086
+ if max == :* then self & (min..)
2087
+ elsif min <= max then self & (min..max)
2088
+ else remain_frozen_empty
2089
+ end
2090
+ end
2091
+ end
2092
+
2093
+ ######################################################################{{{2
2094
+ # Core set data create/freeze/dup primitives
2095
+
2096
+ def new_set_data = []
2097
+ def freeze_set_data = set_data.each(&:freeze).freeze
2098
+ def dup_set_data = set_data.map { _1.dup }
2099
+ protected :dup_set_data
2100
+
2101
+ ######################################################################{{{2
2102
+ # Core set data query/enumeration primitives
2103
+
2104
+ def min_num = minmaxes.first&.first
2105
+ def max_num = minmaxes.last&.last
2106
+
2107
+ def min_at(idx) = minmaxes[idx][0]
2108
+ def max_at(idx) = minmaxes[idx][1]
2109
+
2110
+ ######################################################################{{{2
2111
+ # Core set data modification primitives
2112
+
2113
+ def set_min_at(idx, min) = minmaxes[idx][0] = min
2114
+ def set_max_at(idx, max) = minmaxes[idx][1] = max
2115
+ def replace_minmaxes(other) = minmaxes.replace(other)
2116
+ def append_minmax(min, max) = minmaxes << [min, max]
2117
+ def insert_minmax(idx, min, max) = minmaxes.insert idx, [min, max]
2118
+ def delete_run_at(idx) = runs.delete_at(idx)
2119
+ def slice_runs!(...) = runs.slice!(...)
2120
+ def truncate_runs!(idx) = runs.slice!(idx..)
2121
+
2122
+ ######################################################################{{{2
2123
+ # Update methods
2124
+
1824
2125
  def modifying!
1825
2126
  if frozen?
1826
2127
  raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
1827
2128
  end
1828
2129
  end
1829
2130
 
1830
- def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
1831
- def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
2131
+ def add_minmaxes(minmaxes)
2132
+ minmaxes.each do |minmax|
2133
+ add_minmax minmax
2134
+ end
2135
+ self
2136
+ end
2137
+
2138
+ def subtract_minmaxes(minmaxes)
2139
+ minmaxes.each do |minmax|
2140
+ subtract_minmax minmax
2141
+ end
2142
+ self
2143
+ end
1832
2144
 
1833
2145
  #
1834
- # --|=====| |=====new tuple=====| append
1835
- # ?????????-|=====new tuple=====|-|===lower===|-- insert
2146
+ # --|=====| |=====new run=======| append
2147
+ # ?????????-|=====new run=======|-|===lower===|-- insert
1836
2148
  #
1837
- # |=====new tuple=====|
2149
+ # |=====new run=======|
1838
2150
  # ---------??=======lower=======??--------------- noop
1839
2151
  #
1840
2152
  # ---------??===lower==|--|==| join remaining
1841
2153
  # ---------??===lower==|--|==|----|===upper===|-- join until upper
1842
2154
  # ---------??===lower==|--|==|--|=====upper===|-- join to upper
1843
- def tuple_add(tuple)
2155
+ def add_minmax(minmax)
1844
2156
  modifying!
1845
- min, max = tuple
1846
- lower, lower_idx = tuple_gte_with_index(min - 1)
1847
- if lower.nil? then tuples << [min, max]
1848
- elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1849
- else tuple_coalesce(lower, lower_idx, min, max)
2157
+ min, max = minmax
2158
+ lower_idx = bsearch_index(min - 1)
2159
+ lmin, lmax = min_at(lower_idx), max_at(lower_idx) if lower_idx
2160
+ if lmin.nil? then append_minmax min, max
2161
+ elsif (max + 1) < lmin then insert_minmax lower_idx, min, max
2162
+ else add_coalesced_minmax(lower_idx, lmin, lmax, min, max)
1850
2163
  end
1851
2164
  end
1852
2165
 
1853
- def tuple_coalesce(lower, lower_idx, min, max)
1854
- return if lower.first <= min && max <= lower.last
1855
- lower[0] = [min, lower.first].min
1856
- lower[1] = [max, lower.last].max
1857
- lower_idx += 1
1858
- return if lower_idx == tuples.count
1859
- tmax_adj = lower.last + 1
1860
- upper, upper_idx = tuple_gte_with_index(tmax_adj)
1861
- if upper
1862
- tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last)
2166
+ def add_coalesced_minmax(lower_idx, lmin, lmax, min, max)
2167
+ return if lmin <= min && max <= lmax
2168
+ set_min_at lower_idx, (lmin = min) if min < lmin
2169
+ set_max_at lower_idx, (lmax = max) if lmax < max
2170
+ next_idx = lower_idx + 1
2171
+ return if next_idx == runs.count
2172
+ tmax_adj = lmax + 1
2173
+ if (upper_idx = bsearch_index(tmax_adj))
2174
+ if tmax_adj < min_at(upper_idx)
2175
+ upper_idx -= 1
2176
+ else
2177
+ set_max_at lower_idx, max_at(upper_idx)
2178
+ end
1863
2179
  end
1864
- tuples.slice!(lower_idx..upper_idx)
2180
+ slice_runs! next_idx..upper_idx
1865
2181
  end
1866
2182
 
1867
- # |====tuple================|
2183
+ # |====subtracted run=======|
1868
2184
  # --|====| no more 1. noop
1869
2185
  # --|====|---------------------------|====lower====|-- 2. noop
1870
2186
  # -------|======lower================|---------------- 3. split
@@ -1877,61 +2193,59 @@ module Net
1877
2193
  # -------??=====lower====|--|====| no more 6. delete rest
1878
2194
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
1879
2195
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
1880
- def tuple_subtract(tuple)
2196
+ def subtract_minmax(minmax)
1881
2197
  modifying!
1882
- min, max = tuple
1883
- lower, idx = tuple_gte_with_index(min)
1884
- if lower.nil? then nil # case 1.
1885
- elsif max < lower.first then nil # case 2.
1886
- elsif max < lower.last then tuple_trim_or_split lower, idx, min, max
1887
- else tuples_trim_or_delete lower, idx, min, max
2198
+ min, max = minmax
2199
+ idx = bsearch_index(min)
2200
+ lmin, lmax = min_at(idx), max_at(idx) if idx
2201
+ if lmin.nil? then nil # case 1.
2202
+ elsif max < lmin then nil # case 2.
2203
+ elsif max < lmax then trim_or_split_minmax idx, lmin, min, max
2204
+ else trim_or_delete_minmax idx, lmin, lmax, min, max
1888
2205
  end
1889
2206
  end
1890
2207
 
1891
- def tuple_trim_or_split(lower, idx, tmin, tmax)
1892
- if lower.first < tmin # split
1893
- tuples.insert(idx, [lower.first, tmin - 1])
2208
+ def trim_or_split_minmax(idx, lmin, tmin, tmax)
2209
+ set_min_at idx, tmax + 1
2210
+ if lmin < tmin # split
2211
+ insert_minmax idx, lmin, tmin - 1
1894
2212
  end
1895
- lower[0] = tmax + 1
1896
2213
  end
1897
2214
 
1898
- def tuples_trim_or_delete(lower, lower_idx, tmin, tmax)
1899
- if lower.first < tmin # trim lower
1900
- lower[1] = tmin - 1
2215
+ def trim_or_delete_minmax(lower_idx, lmin, lmax, tmin, tmax)
2216
+ if lmin < tmin # trim lower
2217
+ lmax = set_max_at lower_idx, tmin - 1
1901
2218
  lower_idx += 1
1902
2219
  end
1903
- if tmax == lower.last # case 5
1904
- upper_idx = lower_idx
1905
- elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1))
1906
- upper_idx -= 1 # cases 7 and 8
1907
- upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7)
2220
+ if tmax == lmax # case 5
2221
+ delete_run_at lower_idx
2222
+ elsif (upper_idx = bsearch_index(tmax + 1))
2223
+ if min_at(upper_idx) <= tmax # case 8
2224
+ set_min_at upper_idx, tmax + 1
2225
+ end
2226
+ slice_runs! lower_idx..upper_idx - 1 # cases 7 and 8
2227
+ else # case 6
2228
+ truncate_runs! lower_idx
1908
2229
  end
1909
- tuples.slice!(lower_idx..upper_idx)
1910
- end
1911
-
1912
- def tuple_gte_with_index(num)
1913
- idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx]
1914
2230
  end
1915
2231
 
1916
- def range_gte_to(num)
1917
- first, last = tuples.bsearch { _2 >= num }
1918
- first..last if first
1919
- end
1920
-
1921
- def nz_number(num)
1922
- String === num && !/\A[1-9]\d*\z/.match?(num) and
1923
- raise DataFormatError, "%p is not a valid nz-number" % [num]
1924
- NumValidator.ensure_nz_number Integer num
1925
- rescue TypeError # To catch errors from Integer()
1926
- raise DataFormatError, $!.message
1927
- end
2232
+ alias add_runs add_minmaxes
2233
+ alias add_run add_minmax
2234
+ alias subtract_runs subtract_minmaxes
2235
+ alias subtract_run subtract_minmax
1928
2236
 
2237
+ ######################################################################{{{2
1929
2238
  # intentionally defined after the class implementation
1930
2239
 
2240
+ FULL_SET_DATA = [[1, STAR_INT].freeze].freeze
2241
+ private_constant :FULL_SET_DATA
2242
+
1931
2243
  EMPTY = new.freeze
1932
2244
  FULL = self["1:*"]
1933
2245
  private_constant :EMPTY, :FULL
1934
2246
 
2247
+ # }}}
2248
+ # vim:foldmethod=marker
1935
2249
  end
1936
2250
  end
1937
2251
  end