Class: Immutable::Set
- Inherits:
-
Object
- Object
- Immutable::Set
- Includes:
- Enumerable
- Defined in:
- lib/immutable/_core.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
Setpopulated 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
Setwithitemadded. -
#add?(item) ⇒ Set, false
If
itemis not a member of thisSet, return a newSetwithitemadded. -
#clear ⇒ Set
Return an empty
Setinstance, of the same class as this one. -
#delete(item) ⇒ Set
Return a new
Setwithitemremoved. -
#delete?(item) ⇒ Set, false
If
itemis a member of thisSet, return a newSetwithitemremoved. -
#difference(other) ⇒ Set
(also: #subtract, #-)
Return a new
Setwith all the items inotherremoved. -
#disjoint?(other) ⇒ Boolean
Return
trueif thisSetandotherdo 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
trueif thisSetcontains no items. -
#eql?(other) ⇒ Boolean
(also: #==)
Return true if
otherhas the same type and contents as thisSet. -
#exclusion(other) ⇒ Set
(also: #^)
Return a new
Setwhich contains all the items which are members of thisSetor ofother, 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
trueif the given item is present in thisSet. -
#initialize(items = []) ⇒ Set
constructor
A new instance of Set.
-
#intersect?(other) ⇒ Boolean
Return
trueif thisSetandotherhave at least one item in common. -
#intersection(other) ⇒ Set
(also: #&)
Return a new
Setwhich contains all the items which are members of both thisSetandother. -
#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
trueifothercontains all the items in thisSet, plus at least one item which is not in this set. -
#proper_superset?(other) ⇒ Boolean
(also: #>)
Returns
trueif thisSetcontains all the items inother, plus at least one item which is not inother. -
#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
Setwith 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
trueif all items in thisSetare also inother. -
#superset?(other) ⇒ Boolean
(also: #>=)
Return
trueif all items inotherare also in thisSet. -
#to_set ⇒ self
Return
self. -
#union(other) ⇒ Set
(also: #|, #+, #merge)
Return a new
Setwhich contains all the members of both thisSetandother.
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.
2539 2540 2541 |
# File 'lib/immutable/_core.rb', line 2539 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.
2556 2557 2558 |
# File 'lib/immutable/_core.rb', line 2556 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.
2547 2548 2549 |
# File 'lib/immutable/_core.rb', line 2547 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.
2589 2590 2591 |
# File 'lib/immutable/_core.rb', line 2589 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.
2603 2604 2605 |
# File 'lib/immutable/_core.rb', line 2603 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.
2988 2989 2990 |
# File 'lib/immutable/_core.rb', line 2988 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.
2616 2617 2618 2619 |
# File 'lib/immutable/_core.rb', line 2616 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.
2630 2631 2632 |
# File 'lib/immutable/_core.rb', line 2630 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.
2830 2831 2832 2833 2834 2835 2836 2837 |
# File 'lib/immutable/_core.rb', line 2830 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.
2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 |
# File 'lib/immutable/_core.rb', line 2932 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.
3017 3018 3019 |
# File 'lib/immutable/_core.rb', line 3017 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.
2647 2648 2649 2650 2651 |
# File 'lib/immutable/_core.rb', line 2647 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.
2569 2570 2571 |
# File 'lib/immutable/_core.rb', line 2569 def empty? @trie.empty? end |
#eql?(other) ⇒ Boolean Also known as: ==
Return true if other has the same type and contents as this Set.
2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 |
# File 'lib/immutable/_core.rb', line 2996 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.
2849 2850 2851 |
# File 'lib/immutable/_core.rb', line 2849 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.
2726 2727 2728 |
# File 'lib/immutable/_core.rb', line 2726 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.
2964 2965 2966 2967 2968 2969 |
# File 'lib/immutable/_core.rb', line 2964 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`.
3010 3011 3012 |
# File 'lib/immutable/_core.rb', line 3010 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.
2714 2715 2716 |
# File 'lib/immutable/_core.rb', line 2714 def include?(object) @trie.key?(object) end |
#intersect?(other) ⇒ Boolean
Return true if this Set and other have at least one item in common.
2952 2953 2954 |
# File 'lib/immutable/_core.rb', line 2952 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.
2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 |
# File 'lib/immutable/_core.rb', line 2807 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.
2697 2698 2699 2700 2701 |
# File 'lib/immutable/_core.rb', line 2697 def map return enum_for(:map) if not block_given? return self if empty? self.class.new(super) end |
#marshal_dump ⇒ Object
3033 3034 3035 3036 3037 3038 3039 |
# File 'lib/immutable/_core.rb', line 3033 def marshal_dump output = {} each do |key| output[key] = nil end output end |
#marshal_load(dictionary) ⇒ Object
3042 3043 3044 3045 3046 |
# File 'lib/immutable/_core.rb', line 3042 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.
2901 2902 2903 2904 2905 2906 2907 2908 |
# File 'lib/immutable/_core.rb', line 2901 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.
2920 2921 2922 |
# File 'lib/immutable/_core.rb', line 2920 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.
2666 2667 2668 2669 2670 |
# File 'lib/immutable/_core.rb', line 2666 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.
2980 2981 2982 |
# File 'lib/immutable/_core.rb', line 2980 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.
2679 2680 2681 2682 2683 |
# File 'lib/immutable/_core.rb', line 2679 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.
2575 2576 2577 |
# File 'lib/immutable/_core.rb', line 2575 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.
2744 2745 2746 |
# File 'lib/immutable/_core.rb', line 2744 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.
2762 2763 2764 |
# File 'lib/immutable/_core.rb', line 2762 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.
2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 |
# File 'lib/immutable/_core.rb', line 2861 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.
2887 2888 2889 |
# File 'lib/immutable/_core.rb', line 2887 def superset?(other) other.subset?(self) end |
#to_set ⇒ self
Return self.
3028 3029 3030 |
# File 'lib/immutable/_core.rb', line 3028 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.
2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 |
# File 'lib/immutable/_core.rb', line 2774 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 |