immutable-ruby 0.1.0 → 0.2.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.
data/lib/immutable/set.rb CHANGED
@@ -1,583 +1,3 @@
1
- require 'immutable/undefined'
2
- require 'immutable/enumerable'
3
- require 'immutable/hash'
4
- require 'immutable/trie'
5
- require 'immutable/sorted_set'
6
- require 'set'
7
-
8
- module Immutable
9
-
10
- # `Immutable::Set` is a collection of unordered values with no duplicates. Testing whether
11
- # an object is present in the `Set` can be done in constant time. `Set` is also `Enumerable`, so you can
12
- # iterate over the members of the set with {#each}, transform them with {#map}, filter
13
- # them with {#select}, and so on. Some of the `Enumerable` methods are overridden to
14
- # return `immutable-ruby` collections.
15
- #
16
- # Like the `Set` class in Ruby's standard library, which we will call RubySet,
17
- # `Immutable::Set` defines equivalency of objects using `#hash` and `#eql?`. No two
18
- # objects with the same `#hash` code, and which are also `#eql?`, can coexist in the
19
- # same `Set`. If one is already in the `Set`, attempts to add another one will have
20
- # no effect.
21
- #
22
- # `Set`s have no natural ordering and cannot be compared using `#<=>`. However, they
23
- # define {#<}, {#>}, {#<=}, and {#>=} as shorthand for {#proper_subset?},
24
- # {#proper_superset?}, {#subset?}, and {#superset?} respectively.
25
- #
26
- # The basic set-theoretic operations {#union}, {#intersection}, {#difference}, and
27
- # {#exclusion} work with any `Enumerable` object.
28
- #
29
- # A `Set` can be created in either of the following ways:
30
- #
31
- # Immutable::Set.new([1, 2, 3]) # any Enumerable can be used to initialize
32
- # Immutable::Set['A', 'B', 'C', 'D']
33
- #
34
- # The latter 2 forms of initialization can be used with your own, custom subclasses
35
- # of `Immutable::Set`.
36
- #
37
- # Unlike RubySet, all methods which you might expect to "modify" an `Immutable::Set`
38
- # actually return a new set and leave the existing one unchanged.
39
- #
40
- # @example
41
- # set1 = Immutable::Set[1, 2] # => Immutable::Set[1, 2]
42
- # set2 = Immutable::Set[1, 2] # => Immutable::Set[1, 2]
43
- # set1 == set2 # => true
44
- # set3 = set1.add("foo") # => Immutable::Set[1, 2, "foo"]
45
- # set3 - set2 # => Immutable::Set["foo"]
46
- # set3.subset?(set1) # => false
47
- # set1.subset?(set3) # => true
48
- #
49
- class Set
50
- include Immutable::Enumerable
51
-
52
- class << self
53
- # Create a new `Set` populated with the given items.
54
- # @return [Set]
55
- def [](*items)
56
- items.empty? ? empty : new(items)
57
- end
58
-
59
- # Return an empty `Set`. If used on a subclass, returns an empty instance
60
- # of that class.
61
- #
62
- # @return [Set]
63
- def empty
64
- @empty ||= new
65
- end
66
-
67
- # "Raw" allocation of a new `Set`. Used internally to create a new
68
- # instance quickly after obtaining a modified {Trie}.
69
- #
70
- # @return [Set]
71
- # @private
72
- def alloc(trie = EmptyTrie)
73
- allocate.tap { |s| s.instance_variable_set(:@trie, trie) }.freeze
74
- end
75
- end
76
-
77
- def initialize(items=[])
78
- @trie = Trie.new(0)
79
- items.each { |item| @trie.put!(item, nil) }
80
- freeze
81
- end
82
-
83
- # Return `true` if this `Set` contains no items.
84
- # @return [Boolean]
85
- def empty?
86
- @trie.empty?
87
- end
88
-
89
- # Return the number of items in this `Set`.
90
- # @return [Integer]
91
- def size
92
- @trie.size
93
- end
94
- alias length size
95
-
96
- # Return a new `Set` with `item` added. If `item` is already in the set,
97
- # return `self`.
98
- #
99
- # @example
100
- # Immutable::Set[1, 2, 3].add(4) # => Immutable::Set[1, 2, 4, 3]
101
- # Immutable::Set[1, 2, 3].add(2) # => Immutable::Set[1, 2, 3]
102
- #
103
- # @param item [Object] The object to add
104
- # @return [Set]
105
- def add(item)
106
- include?(item) ? self : self.class.alloc(@trie.put(item, nil))
107
- end
108
- alias << add
109
-
110
- # If `item` is not a member of this `Set`, return a new `Set` with `item` added.
111
- # Otherwise, return `false`.
112
- #
113
- # @example
114
- # Immutable::Set[1, 2, 3].add?(4) # => Immutable::Set[1, 2, 4, 3]
115
- # Immutable::Set[1, 2, 3].add?(2) # => false
116
- #
117
- # @param item [Object] The object to add
118
- # @return [Set, false]
119
- def add?(item)
120
- !include?(item) && add(item)
121
- end
122
-
123
- # Return a new `Set` with `item` removed. If `item` is not a member of the set,
124
- # return `self`.
125
- #
126
- # @example
127
- # Immutable::Set[1, 2, 3].delete(1) # => Immutable::Set[2, 3]
128
- # Immutable::Set[1, 2, 3].delete(99) # => Immutable::Set[1, 2, 3]
129
- #
130
- # @param item [Object] The object to remove
131
- # @return [Set]
132
- def delete(item)
133
- trie = @trie.delete(item)
134
- new_trie(trie)
135
- end
136
-
137
- # If `item` is a member of this `Set`, return a new `Set` with `item` removed.
138
- # Otherwise, return `false`.
139
- #
140
- # @example
141
- # Immutable::Set[1, 2, 3].delete?(1) # => Immutable::Set[2, 3]
142
- # Immutable::Set[1, 2, 3].delete?(99) # => false
143
- #
144
- # @param item [Object] The object to remove
145
- # @return [Set, false]
146
- def delete?(item)
147
- include?(item) && delete(item)
148
- end
149
-
150
- # Call the block once for each item in this `Set`. No specific iteration order
151
- # is guaranteed, but the order will be stable for any particular `Set`. If
152
- # no block is given, an `Enumerator` is returned instead.
153
- #
154
- # @example
155
- # Immutable::Set["Dog", "Elephant", "Lion"].each { |e| puts e }
156
- # Elephant
157
- # Dog
158
- # Lion
159
- # # => Immutable::Set["Dog", "Elephant", "Lion"]
160
- #
161
- # @yield [item] Once for each item.
162
- # @return [self, Enumerator]
163
- def each
164
- return to_enum if not block_given?
165
- @trie.each { |key, _| yield(key) }
166
- self
167
- end
168
-
169
- # Call the block once for each item in this `Set`. Iteration order will be
170
- # the opposite of {#each}. If no block is given, an `Enumerator` is
171
- # returned instead.
172
- #
173
- # @example
174
- # Immutable::Set["Dog", "Elephant", "Lion"].reverse_each { |e| puts e }
175
- # Lion
176
- # Dog
177
- # Elephant
178
- # # => Immutable::Set["Dog", "Elephant", "Lion"]
179
- #
180
- # @yield [item] Once for each item.
181
- # @return [self]
182
- def reverse_each
183
- return enum_for(:reverse_each) if not block_given?
184
- @trie.reverse_each { |key, _| yield(key) }
185
- self
186
- end
187
-
188
- # Return a new `Set` with all the items for which the block returns true.
189
- #
190
- # @example
191
- # Immutable::Set["Elephant", "Dog", "Lion"].select { |e| e.size >= 4 }
192
- # # => Immutable::Set["Elephant", "Lion"]
193
- # @yield [item] Once for each item.
194
- # @return [Set]
195
- def select
196
- return enum_for(:select) unless block_given?
197
- trie = @trie.select { |key, _| yield(key) }
198
- new_trie(trie)
199
- end
200
- alias find_all select
201
- alias keep_if select
202
-
203
- # Call the block once for each item in this `Set`. All the values returned
204
- # from the block will be gathered into a new `Set`. If no block is given,
205
- # an `Enumerator` is returned instead.
206
- #
207
- # @example
208
- # Immutable::Set["Cat", "Elephant", "Dog", "Lion"].map { |e| e.size }
209
- # # => Immutable::Set[8, 4, 3]
210
- #
211
- # @yield [item] Once for each item.
212
- # @return [Set]
213
- def map
214
- return enum_for(:map) if not block_given?
215
- return self if empty?
216
- self.class.new(super)
217
- end
218
- alias collect map
219
-
220
- # Return `true` if the given item is present in this `Set`. More precisely,
221
- # return `true` if an object with the same `#hash` code, and which is also `#eql?`
222
- # to the given object is present.
223
- #
224
- # @example
225
- # Immutable::Set["A", "B", "C"].include?("B") # => true
226
- # Immutable::Set["A", "B", "C"].include?("Z") # => false
227
- #
228
- # @param object [Object] The object to check for
229
- # @return [Boolean]
230
- def include?(object)
231
- @trie.key?(object)
232
- end
233
- alias member? include?
234
-
235
- # Return a member of this `Set`. The member chosen will be the first one which
236
- # would be yielded by {#each}. If the set is empty, return `nil`.
237
- #
238
- # @example
239
- # Immutable::Set["A", "B", "C"].first # => "C"
240
- #
241
- # @return [Object]
242
- def first
243
- (entry = @trie.at(0)) && entry[0]
244
- end
245
-
246
- # Return a {SortedSet} which contains the same items as this `Set`, ordered by
247
- # the given comparator block.
248
- #
249
- # @example
250
- # Immutable::Set["Elephant", "Dog", "Lion"].sort
251
- # # => Immutable::SortedSet["Dog", "Elephant", "Lion"]
252
- # Immutable::Set["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size }
253
- # # => Immutable::SortedSet["Dog", "Lion", "Elephant"]
254
- #
255
- # @yield [a, b] Any number of times with different pairs of elements.
256
- # @yieldreturn [Integer] Negative if the first element should be sorted
257
- # lower, positive if the latter element, or 0 if
258
- # equal.
259
- # @return [SortedSet]
260
- def sort(&comparator)
261
- SortedSet.new(to_a, &comparator)
262
- end
263
-
264
- # Return a {SortedSet} which contains the same items as this `Set`, ordered
265
- # by mapping each item through the provided block to obtain sort keys, and
266
- # then sorting the keys.
267
- #
268
- # @example
269
- # Immutable::Set["Elephant", "Dog", "Lion"].sort_by { |e| e.size }
270
- # # => Immutable::SortedSet["Dog", "Lion", "Elephant"]
271
- #
272
- # @yield [item] Once for each item to create the set, and then potentially
273
- # again depending on what operations are performed on the
274
- # returned {SortedSet}. As such, it is recommended that the
275
- # block be a pure function.
276
- # @yieldreturn [Object] sort key for the item
277
- # @return [SortedSet]
278
- def sort_by(&mapper)
279
- SortedSet.new(to_a, &mapper)
280
- end
281
-
282
- # Return a new `Set` which contains all the members of both this `Set` and `other`.
283
- # `other` can be any `Enumerable` object.
284
- #
285
- # @example
286
- # Immutable::Set[1, 2] | Immutable::Set[2, 3] # => Immutable::Set[1, 2, 3]
287
- #
288
- # @param other [Enumerable] The collection to merge with
289
- # @return [Set]
290
- def union(other)
291
- if other.is_a?(Immutable::Set)
292
- if other.size > size
293
- small_set_pairs = @trie
294
- large_set_trie = other.instance_variable_get(:@trie)
295
- else
296
- small_set_pairs = other.instance_variable_get(:@trie)
297
- large_set_trie = @trie
298
- end
299
- else
300
- if other.respond_to?(:lazy)
301
- small_set_pairs = other.lazy.map { |e| [e, nil] }
302
- else
303
- small_set_pairs = other.map { |e| [e, nil] }
304
- end
305
- large_set_trie = @trie
306
- end
307
-
308
- trie = large_set_trie.bulk_put(small_set_pairs)
309
- new_trie(trie)
310
- end
311
- alias | union
312
- alias + union
313
- alias merge union
314
-
315
- # Return a new `Set` which contains all the items which are members of both
316
- # this `Set` and `other`. `other` can be any `Enumerable` object.
317
- #
318
- # @example
319
- # Immutable::Set[1, 2] & Immutable::Set[2, 3] # => Immutable::Set[2]
320
- #
321
- # @param other [Enumerable] The collection to intersect with
322
- # @return [Set]
323
- def intersection(other)
324
- if other.size < @trie.size
325
- if other.is_a?(Immutable::Set)
326
- trie = other.instance_variable_get(:@trie).select { |key, _| include?(key) }
327
- else
328
- trie = Trie.new(0)
329
- other.each { |obj| trie.put!(obj, nil) if include?(obj) }
330
- end
331
- else
332
- trie = @trie.select { |key, _| other.include?(key) }
333
- end
334
- new_trie(trie)
335
- end
336
- alias & intersection
337
-
338
- # Return a new `Set` with all the items in `other` removed. `other` can be
339
- # any `Enumerable` object.
340
- #
341
- # @example
342
- # Immutable::Set[1, 2] - Immutable::Set[2, 3] # => Immutable::Set[1]
343
- #
344
- # @param other [Enumerable] The collection to subtract from this set
345
- # @return [Set]
346
- def difference(other)
347
- trie = if (@trie.size <= other.size) && (other.is_a?(Immutable::Set) || (defined?(::Set) && other.is_a?(::Set)))
348
- @trie.select { |key, _| !other.include?(key) }
349
- else
350
- @trie.bulk_delete(other)
351
- end
352
- new_trie(trie)
353
- end
354
- alias subtract difference
355
- alias - difference
356
-
357
- # Return a new `Set` which contains all the items which are members of this
358
- # `Set` or of `other`, but not both. `other` can be any `Enumerable` object.
359
- #
360
- # @example
361
- # Immutable::Set[1, 2] ^ Immutable::Set[2, 3] # => Immutable::Set[1, 3]
362
- #
363
- # @param other [Enumerable] The collection to take the exclusive disjunction of
364
- # @return [Set]
365
- def exclusion(other)
366
- ((self | other) - (self & other))
367
- end
368
- alias ^ exclusion
369
-
370
- # Return `true` if all items in this `Set` are also in `other`.
371
- #
372
- # @example
373
- # Immutable::Set[2, 3].subset?(Immutable::Set[1, 2, 3]) # => true
374
- #
375
- # @param other [Set]
376
- # @return [Boolean]
377
- def subset?(other)
378
- return false if other.size < size
379
-
380
- # This method has the potential to be very slow if 'other' is a large Array, so to avoid that,
381
- # we convert those Arrays to Sets before checking presence of items
382
- # Time to convert Array -> Set is linear in array.size
383
- # Time to check for presence of all items in an Array is proportional to set.size * array.size
384
- # Note that both sides of that equation have array.size -- hence those terms cancel out,
385
- # and the break-even point is solely dependent on the size of this collection
386
- # After doing some benchmarking to estimate the constants, it appears break-even is at ~190 items
387
- # We also check other.size, to avoid the more expensive #is_a? checks in cases where it doesn't matter
388
- #
389
- if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set))
390
- other = ::Set.new(other)
391
- end
392
- all? { |item| other.include?(item) }
393
- end
394
- alias <= subset?
395
-
396
- # Return `true` if all items in `other` are also in this `Set`.
397
- #
398
- # @example
399
- # Immutable::Set[1, 2, 3].superset?(Immutable::Set[2, 3]) # => true
400
- #
401
- # @param other [Set]
402
- # @return [Boolean]
403
- def superset?(other)
404
- other.subset?(self)
405
- end
406
- alias >= superset?
407
-
408
- # Returns `true` if `other` contains all the items in this `Set`, plus at least
409
- # one item which is not in this set.
410
- #
411
- # @example
412
- # Immutable::Set[2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => true
413
- # Immutable::Set[1, 2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => false
414
- #
415
- # @param other [Set]
416
- # @return [Boolean]
417
- def proper_subset?(other)
418
- return false if other.size <= size
419
- # See comments above
420
- if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set))
421
- other = ::Set.new(other)
422
- end
423
- all? { |item| other.include?(item) }
424
- end
425
- alias < proper_subset?
426
-
427
- # Returns `true` if this `Set` contains all the items in `other`, plus at least
428
- # one item which is not in `other`.
429
- #
430
- # @example
431
- # Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[2, 3]) # => true
432
- # Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[1, 2, 3]) # => false
433
- #
434
- # @param other [Set]
435
- # @return [Boolean]
436
- def proper_superset?(other)
437
- other.proper_subset?(self)
438
- end
439
- alias > proper_superset?
440
-
441
- # Return `true` if this `Set` and `other` do not share any items.
442
- #
443
- # @example
444
- # Immutable::Set[1, 2].disjoint?(Immutable::Set[8, 9]) # => true
445
- #
446
- # @param other [Set]
447
- # @return [Boolean]
448
- def disjoint?(other)
449
- if other.size <= size
450
- other.each { |item| return false if include?(item) }
451
- else
452
- # See comment on #subset?
453
- if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set))
454
- other = ::Set.new(other)
455
- end
456
- each { |item| return false if other.include?(item) }
457
- end
458
- true
459
- end
460
-
461
- # Return `true` if this `Set` and `other` have at least one item in common.
462
- #
463
- # @example
464
- # Immutable::Set[1, 2].intersect?(Immutable::Set[2, 3]) # => true
465
- #
466
- # @param other [Set]
467
- # @return [Boolean]
468
- def intersect?(other)
469
- !disjoint?(other)
470
- end
471
-
472
- # Recursively insert the contents of any nested `Set`s into this `Set`, and
473
- # remove them.
474
- #
475
- # @example
476
- # Immutable::Set[Immutable::Set[1, 2], Immutable::Set[3, 4]].flatten
477
- # # => Immutable::Set[1, 2, 3, 4]
478
- #
479
- # @return [Set]
480
- def flatten
481
- reduce(self.class.empty) do |set, item|
482
- next set.union(item.flatten) if item.is_a?(Set)
483
- set.add(item)
484
- end
485
- end
486
-
487
- alias group group_by
488
- alias classify group_by
489
-
490
- # Return a randomly chosen item from this `Set`. If the set is empty, return `nil`.
491
- #
492
- # @example
493
- # Immutable::Set[1, 2, 3, 4, 5].sample # => 3
494
- #
495
- # @return [Object]
496
- def sample
497
- empty? ? nil : @trie.at(rand(size))[0]
498
- end
499
-
500
- # Return an empty `Set` instance, of the same class as this one. Useful if you
501
- # have multiple subclasses of `Set` and want to treat them polymorphically.
502
- #
503
- # @return [Set]
504
- def clear
505
- self.class.empty
506
- end
507
-
508
- # Return true if `other` has the same type and contents as this `Set`.
509
- #
510
- # @param other [Object] The object to compare with
511
- # @return [Boolean]
512
- def eql?(other)
513
- return true if other.equal?(self)
514
- return false if not instance_of?(other.class)
515
- other_trie = other.instance_variable_get(:@trie)
516
- return false if @trie.size != other_trie.size
517
- @trie.each do |key, _|
518
- return false if !other_trie.key?(key)
519
- end
520
- true
521
- end
522
- alias == eql?
523
-
524
- # See `Object#hash`.
525
- # @return [Integer]
526
- def hash
527
- reduce(0) { |hash, item| (hash << 5) - hash + item.hash }
528
- end
529
-
530
- # Return `self`. Since this is an immutable object duplicates are
531
- # equivalent.
532
- # @return [Set]
533
- def dup
534
- self
535
- end
536
- alias clone dup
537
-
538
- undef :"<=>" # Sets are not ordered, so Enumerable#<=> will give a meaningless result
539
- undef :each_index # Set members cannot be accessed by 'index', so #each_index is not meaningful
540
-
541
- # Return `self`.
542
- #
543
- # @return [self]
544
- def to_set
545
- self
546
- end
547
-
548
- # @private
549
- def marshal_dump
550
- output = {}
551
- each do |key|
552
- output[key] = nil
553
- end
554
- output
555
- end
556
-
557
- # @private
558
- def marshal_load(dictionary)
559
- @trie = dictionary.reduce(EmptyTrie) do |trie, key_value|
560
- trie.put(key_value.first, nil)
561
- end
562
- end
563
-
564
- private
565
-
566
- def new_trie(trie)
567
- if trie.empty?
568
- self.class.empty
569
- elsif trie.equal?(@trie)
570
- self
571
- else
572
- self.class.alloc(trie)
573
- end
574
- end
575
- end
576
-
577
- # The canonical empty `Set`. Returned by `Set[]` when
578
- # invoked with no arguments; also returned by `Set.empty`. Prefer using this
579
- # one rather than creating many empty sets using `Set.new`.
580
- #
581
- # @private
582
- EmptySet = Immutable::Set.empty
583
- end
1
+ # Definition of Immutable::Vector is in a separate file to avoid
2
+ # circular dependency warnings
3
+ require 'immutable/_core'
@@ -955,8 +955,6 @@ module Immutable
955
955
  return false if !a.next.eql?(b.next)
956
956
  end
957
957
  true
958
- rescue StopIteration
959
- true
960
958
  end
961
959
 
962
960
  # See `Object#hash`.