Class: Immutable::Set
- Inherits:
- 
      Object
      
        - Object
- Immutable::Set
 
- Includes:
- Enumerable
- Defined in:
- lib/immutable/set.rb
Overview
‘Immutable::Set` is a collection of unordered values with no duplicates. Testing whether an object is present in the `Set` can be done in constant time. `Set` is also `Enumerable`, so you can iterate over the members of the set with #each, transform them with #map, filter them with #select, and so on. Some of the `Enumerable` methods are overridden to return `immutable-ruby` collections.
Like the ‘Set` class in Ruby’s standard library, which we will call RubySet, ‘Immutable::Set` defines equivalency of objects using `#hash` and `#eql?`. No two objects with the same `#hash` code, and which are also `#eql?`, can coexist in the same `Set`. If one is already in the `Set`, attempts to add another one will have no effect.
‘Set`s have no natural ordering and cannot be compared using `#<=>`. However, they define #<, #>, #<=, and #>= as shorthand for #proper_subset?, #proper_superset?, #subset?, and #superset? respectively.
The basic set-theoretic operations #union, #intersection, #difference, and #exclusion work with any ‘Enumerable` object.
A ‘Set` can be created in either of the following ways:
Immutable::Set.new([1, 2, 3]) # any Enumerable can be used to initialize
Immutable::Set['A', 'B', 'C', 'D']
The latter 2 forms of initialization can be used with your own, custom subclasses of ‘Immutable::Set`.
Unlike RubySet, all methods which you might expect to “modify” an ‘Immutable::Set` actually return a new set and leave the existing one unchanged.
Class Method Summary collapse
- 
  
    
      .[](*items)  ⇒ Set 
    
    
  
  
  
  
  
  
  
  
  
    Create a new ‘Set` populated with the given items. 
- 
  
    
      .alloc(trie = EmptyTrie)  ⇒ Set 
    
    
  
  
  
  
  
  
  
  
  
    “Raw” allocation of a new ‘Set`. 
- 
  
    
      .empty  ⇒ Set 
    
    
  
  
  
  
  
  
  
  
  
    Return an empty ‘Set`. 
Instance Method Summary collapse
- 
  
    
      #add(item)  ⇒ Set 
    
    
      (also: #<<)
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` with `item` added. 
- 
  
    
      #add?(item)  ⇒ Set, false 
    
    
  
  
  
  
  
  
  
  
  
    If ‘item` is not a member of this `Set`, return a new `Set` with `item` added. 
- 
  
    
      #clear  ⇒ Set 
    
    
  
  
  
  
  
  
  
  
  
    Return an empty ‘Set` instance, of the same class as this one. 
- 
  
    
      #delete(item)  ⇒ Set 
    
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` with `item` removed. 
- 
  
    
      #delete?(item)  ⇒ Set, false 
    
    
  
  
  
  
  
  
  
  
  
    If ‘item` is a member of this `Set`, return a new `Set` with `item` removed. 
- 
  
    
      #difference(other)  ⇒ Set 
    
    
      (also: #subtract, #-)
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` with all the items in `other` removed. 
- 
  
    
      #disjoint?(other)  ⇒ Boolean 
    
    
  
  
  
  
  
  
  
  
  
    Return ‘true` if this `Set` and `other` do not share any items. 
- 
  
    
      #dup  ⇒ Set 
    
    
      (also: #clone)
    
  
  
  
  
  
  
  
  
  
    Return ‘self`. 
- 
  
    
      #each {|item| ... } ⇒ self, Enumerator 
    
    
  
  
  
  
  
  
  
  
  
    Call the block once for each item in this ‘Set`. 
- 
  
    
      #empty?  ⇒ Boolean 
    
    
  
  
  
  
  
  
  
  
  
    Return ‘true` if this `Set` contains no items. 
- 
  
    
      #eql?(other)  ⇒ Boolean 
    
    
      (also: #==)
    
  
  
  
  
  
  
  
  
  
    Return true if ‘other` has the same type and contents as this `Set`. 
- 
  
    
      #exclusion(other)  ⇒ Set 
    
    
      (also: #^)
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` which contains all the items which are members of this `Set` or of `other`, but not both. 
- 
  
    
      #first  ⇒ Object 
    
    
  
  
  
  
  
  
  
  
  
    Return a member of this ‘Set`. 
- 
  
    
      #flatten  ⇒ Set 
    
    
  
  
  
  
  
  
  
  
  
    Recursively insert the contents of any nested ‘Set`s into this `Set`, and remove them. 
- 
  
    
      #hash  ⇒ Integer 
    
    
  
  
  
  
  
  
  
  
  
    See ‘Object#hash`. 
- 
  
    
      #include?(object)  ⇒ Boolean 
    
    
      (also: #member?)
    
  
  
  
  
  
  
  
  
  
    Return ‘true` if the given item is present in this `Set`. 
- 
  
    
      #initialize(items = [])  ⇒ Set 
    
    
  
  
  
    constructor
  
  
  
  
  
  
  
    A new instance of Set. 
- 
  
    
      #intersect?(other)  ⇒ Boolean 
    
    
  
  
  
  
  
  
  
  
  
    Return ‘true` if this `Set` and `other` have at least one item in common. 
- 
  
    
      #intersection(other)  ⇒ Set 
    
    
      (also: #&)
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` which contains all the items which are members of both this `Set` and `other`. 
- 
  
    
      #map {|item| ... } ⇒ Set 
    
    
      (also: #collect)
    
  
  
  
  
  
  
  
  
  
    Call the block once for each item in this ‘Set`. 
- #marshal_dump ⇒ Object
- #marshal_load(dictionary) ⇒ Object
- 
  
    
      #proper_subset?(other)  ⇒ Boolean 
    
    
      (also: #<)
    
  
  
  
  
  
  
  
  
  
    Returns ‘true` if `other` contains all the items in this `Set`, plus at least one item which is not in this set. 
- 
  
    
      #proper_superset?(other)  ⇒ Boolean 
    
    
      (also: #>)
    
  
  
  
  
  
  
  
  
  
    Returns ‘true` if this `Set` contains all the items in `other`, plus at least one item which is not in `other`. 
- 
  
    
      #reverse_each {|item| ... } ⇒ self 
    
    
  
  
  
  
  
  
  
  
  
    Call the block once for each item in this ‘Set`. 
- 
  
    
      #sample  ⇒ Object 
    
    
  
  
  
  
  
  
  
  
  
    Return a randomly chosen item from this ‘Set`. 
- 
  
    
      #select {|item| ... } ⇒ Set 
    
    
      (also: #find_all, #keep_if)
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` with all the items for which the block returns true. 
- 
  
    
      #size  ⇒ Integer 
    
    
      (also: #length)
    
  
  
  
  
  
  
  
  
  
    Return the number of items in this ‘Set`. 
- 
  
    
      #sort {|a, b| ... } ⇒ SortedSet 
    
    
  
  
  
  
  
  
  
  
  
    Return a SortedSet which contains the same items as this ‘Set`, ordered by the given comparator block. 
- 
  
    
      #sort_by {|item| ... } ⇒ SortedSet 
    
    
  
  
  
  
  
  
  
  
  
    Return a SortedSet which contains the same items as this ‘Set`, ordered by mapping each item through the provided block to obtain sort keys, and then sorting the keys. 
- 
  
    
      #subset?(other)  ⇒ Boolean 
    
    
      (also: #<=)
    
  
  
  
  
  
  
  
  
  
    Return ‘true` if all items in this `Set` are also in `other`. 
- 
  
    
      #superset?(other)  ⇒ Boolean 
    
    
      (also: #>=)
    
  
  
  
  
  
  
  
  
  
    Return ‘true` if all items in `other` are also in this `Set`. 
- 
  
    
      #to_set  ⇒ self 
    
    
  
  
  
  
  
  
  
  
  
    Return ‘self`. 
- 
  
    
      #union(other)  ⇒ Set 
    
    
      (also: #|, #+, #merge)
    
  
  
  
  
  
  
  
  
  
    Return a new ‘Set` which contains all the members of both this `Set` and `other`. 
Methods included from Enumerable
#<=>, #compact, #each_index, #grep, #grep_v, #group_by, #inspect, #join, #partition, #pretty_print, #product, #reject, #sum
Methods included from Enumerable
Constructor Details
Class Method Details
.[](*items) ⇒ Set
Create a new ‘Set` populated with the given items.
| 55 56 57 | # File 'lib/immutable/set.rb', line 55 def [](*items) items.empty? ? empty : new(items) end | 
.alloc(trie = EmptyTrie) ⇒ Set
“Raw” allocation of a new ‘Set`. Used internally to create a new instance quickly after obtaining a modified Trie.
| 72 73 74 | # File 'lib/immutable/set.rb', line 72 def alloc(trie = EmptyTrie) allocate.tap { |s| s.instance_variable_set(:@trie, trie) }.freeze end | 
.empty ⇒ Set
Return an empty ‘Set`. If used on a subclass, returns an empty instance of that class.
| 63 64 65 | # File 'lib/immutable/set.rb', line 63 def empty @empty ||= new end | 
Instance Method Details
#add(item) ⇒ Set Also known as: <<
Return a new ‘Set` with `item` added. If `item` is already in the set, return `self`.
| 105 106 107 | # File 'lib/immutable/set.rb', line 105 def add(item) include?(item) ? self : self.class.alloc(@trie.put(item, nil)) end | 
#add?(item) ⇒ Set, false
If ‘item` is not a member of this `Set`, return a new `Set` with `item` added. Otherwise, return `false`.
| 119 120 121 | # File 'lib/immutable/set.rb', line 119 def add?(item) !include?(item) && add(item) end | 
#clear ⇒ Set
Return an empty ‘Set` instance, of the same class as this one. Useful if you have multiple subclasses of `Set` and want to treat them polymorphically.
| 504 505 506 | # File 'lib/immutable/set.rb', line 504 def clear self.class.empty end | 
#delete(item) ⇒ Set
Return a new ‘Set` with `item` removed. If `item` is not a member of the set, return `self`.
| 132 133 134 135 | # File 'lib/immutable/set.rb', line 132 def delete(item) trie = @trie.delete(item) new_trie(trie) end | 
#delete?(item) ⇒ Set, false
If ‘item` is a member of this `Set`, return a new `Set` with `item` removed. Otherwise, return `false`.
| 146 147 148 | # File 'lib/immutable/set.rb', line 146 def delete?(item) include?(item) && delete(item) end | 
#difference(other) ⇒ Set Also known as: subtract, -
Return a new ‘Set` with all the items in `other` removed. `other` can be any `Enumerable` object.
| 346 347 348 349 350 351 352 353 | # File 'lib/immutable/set.rb', line 346 def difference(other) trie = if (@trie.size <= other.size) && (other.is_a?(Immutable::Set) || (defined?(::Set) && other.is_a?(::Set))) @trie.select { |key, _| !other.include?(key) } else @trie.bulk_delete(other) end new_trie(trie) end | 
#disjoint?(other) ⇒ Boolean
Return ‘true` if this `Set` and `other` do not share any items.
| 448 449 450 451 452 453 454 455 456 457 458 459 | # File 'lib/immutable/set.rb', line 448 def disjoint?(other) if other.size <= size other.each { |item| return false if include?(item) } else # See comment on #subset? if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) other = ::Set.new(other) end each { |item| return false if other.include?(item) } end true end | 
#dup ⇒ Set Also known as: clone
Return ‘self`. Since this is an immutable object duplicates are equivalent.
| 533 534 535 | # File 'lib/immutable/set.rb', line 533 def dup self end | 
#each {|item| ... } ⇒ self, Enumerator
Call the block once for each item in this ‘Set`. No specific iteration order is guaranteed, but the order will be stable for any particular `Set`. If no block is given, an `Enumerator` is returned instead.
| 163 164 165 166 167 | # File 'lib/immutable/set.rb', line 163 def each return to_enum if not block_given? @trie.each { |key, _| yield(key) } self end | 
#empty? ⇒ Boolean
Return ‘true` if this `Set` contains no items.
| 85 86 87 | # File 'lib/immutable/set.rb', line 85 def empty? @trie.empty? end | 
#eql?(other) ⇒ Boolean Also known as: ==
Return true if ‘other` has the same type and contents as this `Set`.
| 512 513 514 515 516 517 518 519 520 521 | # File 'lib/immutable/set.rb', line 512 def eql?(other) return true if other.equal?(self) return false if not instance_of?(other.class) other_trie = other.instance_variable_get(:@trie) return false if @trie.size != other_trie.size @trie.each do |key, _| return false if !other_trie.key?(key) end true end | 
#exclusion(other) ⇒ Set Also known as: ^
Return a new ‘Set` which contains all the items which are members of this `Set` or of `other`, but not both. `other` can be any `Enumerable` object.
| 365 366 367 | # File 'lib/immutable/set.rb', line 365 def exclusion(other) ((self | other) - (self & other)) end | 
#first ⇒ Object
Return a member of this ‘Set`. The member chosen will be the first one which would be yielded by #each. If the set is empty, return `nil`.
| 242 243 244 | # File 'lib/immutable/set.rb', line 242 def first (entry = @trie.at(0)) && entry[0] end | 
#flatten ⇒ Set
Recursively insert the contents of any nested ‘Set`s into this `Set`, and remove them.
| 480 481 482 483 484 485 | # File 'lib/immutable/set.rb', line 480 def flatten reduce(self.class.empty) do |set, item| next set.union(item.flatten) if item.is_a?(Set) set.add(item) end end | 
#hash ⇒ Integer
See ‘Object#hash`.
| 526 527 528 | # File 'lib/immutable/set.rb', line 526 def hash reduce(0) { |hash, item| (hash << 5) - hash + item.hash } end | 
#include?(object) ⇒ Boolean Also known as: member?
Return ‘true` if the given item is present in this `Set`. More precisely, return `true` if an object with the same `#hash` code, and which is also `#eql?` to the given object is present.
| 230 231 232 | # File 'lib/immutable/set.rb', line 230 def include?(object) @trie.key?(object) end | 
#intersect?(other) ⇒ Boolean
Return ‘true` if this `Set` and `other` have at least one item in common.
| 468 469 470 | # File 'lib/immutable/set.rb', line 468 def intersect?(other) !disjoint?(other) end | 
#intersection(other) ⇒ Set Also known as: &
Return a new ‘Set` which contains all the items which are members of both this `Set` and `other`. `other` can be any `Enumerable` object.
| 323 324 325 326 327 328 329 330 331 332 333 334 335 | # File 'lib/immutable/set.rb', line 323 def intersection(other) if other.size < @trie.size if other.is_a?(Immutable::Set) trie = other.instance_variable_get(:@trie).select { |key, _| include?(key) } else trie = Trie.new(0) other.each { |obj| trie.put!(obj, nil) if include?(obj) } end else trie = @trie.select { |key, _| other.include?(key) } end new_trie(trie) end | 
#map {|item| ... } ⇒ Set Also known as: collect
Call the block once for each item in this ‘Set`. All the values returned from the block will be gathered into a new `Set`. If no block is given, an `Enumerator` is returned instead.
| 213 214 215 216 217 | # File 'lib/immutable/set.rb', line 213 def map return enum_for(:map) if not block_given? return self if empty? self.class.new(super) end | 
#marshal_dump ⇒ Object
| 549 550 551 552 553 554 555 | # File 'lib/immutable/set.rb', line 549 def marshal_dump output = {} each do |key| output[key] = nil end output end | 
#marshal_load(dictionary) ⇒ Object
| 558 559 560 561 562 | # File 'lib/immutable/set.rb', line 558 def marshal_load(dictionary) @trie = dictionary.reduce(EmptyTrie) do |trie, key_value| trie.put(key_value.first, nil) end end | 
#proper_subset?(other) ⇒ Boolean Also known as: <
Returns ‘true` if `other` contains all the items in this `Set`, plus at least one item which is not in this set.
| 417 418 419 420 421 422 423 424 | # File 'lib/immutable/set.rb', line 417 def proper_subset?(other) return false if other.size <= size # See comments above if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) other = ::Set.new(other) end all? { |item| other.include?(item) } end | 
#proper_superset?(other) ⇒ Boolean Also known as: >
Returns ‘true` if this `Set` contains all the items in `other`, plus at least one item which is not in `other`.
| 436 437 438 | # File 'lib/immutable/set.rb', line 436 def proper_superset?(other) other.proper_subset?(self) end | 
#reverse_each {|item| ... } ⇒ self
Call the block once for each item in this ‘Set`. Iteration order will be the opposite of #each. If no block is given, an `Enumerator` is returned instead.
| 182 183 184 185 186 | # File 'lib/immutable/set.rb', line 182 def reverse_each return enum_for(:reverse_each) if not block_given? @trie.reverse_each { |key, _| yield(key) } self end | 
#sample ⇒ Object
Return a randomly chosen item from this ‘Set`. If the set is empty, return `nil`.
| 496 497 498 | # File 'lib/immutable/set.rb', line 496 def sample empty? ? nil : @trie.at(rand(size))[0] end | 
#select {|item| ... } ⇒ Set Also known as: find_all, keep_if
Return a new ‘Set` with all the items for which the block returns true.
| 195 196 197 198 199 | # File 'lib/immutable/set.rb', line 195 def select return enum_for(:select) unless block_given? trie = @trie.select { |key, _| yield(key) } new_trie(trie) end | 
#size ⇒ Integer Also known as: length
Return the number of items in this ‘Set`.
| 91 92 93 | # File 'lib/immutable/set.rb', line 91 def size @trie.size end | 
#sort {|a, b| ... } ⇒ SortedSet
Return a Immutable::SortedSet which contains the same items as this ‘Set`, ordered by the given comparator block.
| 260 261 262 | # File 'lib/immutable/set.rb', line 260 def sort(&comparator) SortedSet.new(to_a, &comparator) end | 
#sort_by {|item| ... } ⇒ SortedSet
Return a Immutable::SortedSet which contains the same items as this ‘Set`, ordered by mapping each item through the provided block to obtain sort keys, and then sorting the keys.
| 278 279 280 | # File 'lib/immutable/set.rb', line 278 def sort_by(&mapper) SortedSet.new(to_a, &mapper) end | 
#subset?(other) ⇒ Boolean Also known as: <=
Return ‘true` if all items in this `Set` are also in `other`.
| 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 | # File 'lib/immutable/set.rb', line 377 def subset?(other) return false if other.size < size # This method has the potential to be very slow if 'other' is a large Array, so to avoid that, # we convert those Arrays to Sets before checking presence of items # Time to convert Array -> Set is linear in array.size # Time to check for presence of all items in an Array is proportional to set.size * array.size # Note that both sides of that equation have array.size -- hence those terms cancel out, # and the break-even point is solely dependent on the size of this collection # After doing some benchmarking to estimate the constants, it appears break-even is at ~190 items # We also check other.size, to avoid the more expensive #is_a? checks in cases where it doesn't matter # if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) other = ::Set.new(other) end all? { |item| other.include?(item) } end | 
#superset?(other) ⇒ Boolean Also known as: >=
Return ‘true` if all items in `other` are also in this `Set`.
| 403 404 405 | # File 'lib/immutable/set.rb', line 403 def superset?(other) other.subset?(self) end | 
#to_set ⇒ self
Return ‘self`.
| 544 545 546 | # File 'lib/immutable/set.rb', line 544 def to_set self end | 
#union(other) ⇒ Set Also known as: |, +, merge
Return a new ‘Set` which contains all the members of both this `Set` and `other`. `other` can be any `Enumerable` object.
| 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | # File 'lib/immutable/set.rb', line 290 def union(other) if other.is_a?(Immutable::Set) if other.size > size small_set_pairs = @trie large_set_trie = other.instance_variable_get(:@trie) else small_set_pairs = other.instance_variable_get(:@trie) large_set_trie = @trie end else if other.respond_to?(:lazy) small_set_pairs = other.lazy.map { |e| [e, nil] } else small_set_pairs = other.map { |e| [e, nil] } end large_set_trie = @trie end trie = large_set_trie.bulk_put(small_set_pairs) new_trie(trie) end |