Class: Immutable::Hash
- Inherits:
-
Object
- Object
- Immutable::Hash
- Includes:
- Enumerable
- Defined in:
- lib/immutable/hash.rb
Overview
An Immutable::Hash maps a set of unique keys to corresponding values, much like a dictionary maps from words to definitions. Given a key, it can store and retrieve an associated value in constant time. If an existing key is stored again, the new value will replace the old. It behaves much like Ruby’s built-in Hash, which we will call RubyHash for clarity. Like RubyHash, two keys that are #eql? to each other and have the same #hash are considered identical in an Immutable::Hash.
An Immutable::Hash can be created in a couple of ways:
Immutable::Hash.new(font_size: 10, font_family: 'Arial')
Immutable::Hash[first_name: 'John', last_name: 'Smith']
Any Enumerable object which yields two-element ‘[key, value]` arrays can be used to initialize an Immutable::Hash:
Immutable::Hash.new([[:first_name, 'John'], [:last_name, 'Smith']])
Key/value pairs can be added using #put. A new hash is returned and the existing one is left unchanged:
hash = Immutable::Hash[a: 100, b: 200]
hash.put(:c, 500) # => Immutable::Hash[:a => 100, :b => 200, :c => 500]
hash # => Immutable::Hash[:a => 100, :b => 200]
#put can also take a block, which is used to calculate the value to be stored.
hash.put(:a) { |current| current + 200 } # => Immutable::Hash[:a => 300, :b => 200]
Since it is immutable, all methods which you might expect to “modify” a Immutable::Hash actually return a new hash and leave the existing one unchanged. This means that the ‘hash = value` syntax from RubyHash cannot be used with Immutable::Hash.
Nested data structures can easily be updated using #update_in:
hash = Immutable::Hash["a" => Immutable::Vector[Immutable::Hash["c" => 42]]]
hash.update_in("a", 0, "c") { |value| value + 5 }
# => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]]
While an Immutable::Hash can iterate over its keys or values, it does not guarantee any specific iteration order (unlike RubyHash). Methods like #flatten do not guarantee the order of returned key/value pairs.
Like RubyHash, an Immutable::Hash can have a default block which is used when looking up a key that does not exist. Unlike RubyHash, the default block will only be passed the missing key, without the hash itself:
hash = Immutable::Hash.new { |missing_key| missing_key * 10 }
hash[5] # => 50
Class Method Summary collapse
-
.[](pairs = nil) ⇒ Hash
Create a new
Hashpopulated with the given key/value pairs. -
.alloc(trie = EmptyTrie, block = nil) ⇒ Hash
“Raw” allocation of a new
Hash. -
.empty ⇒ Hash
Return an empty
Hash.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Return true if
otherhas the same contents as thisHash. -
#assoc(obj) ⇒ Array
Searches through the
Hash, comparingobjwith each key (using ‘#==`). -
#clear ⇒ Hash
Return an empty
Hashinstance, of the same class as this one. -
#default_proc ⇒ Proc
Return the default block if there is one.
-
#delete(key) ⇒ Hash
Return a new
Hashwithkeyremoved. -
#dup ⇒ Hash
(also: #clone)
Return
self. -
#each {|key, value| ... } ⇒ self
(also: #each_pair)
Call the block once for each key/value pair in this
Hash, passing the key/value pair as parameters. -
#each_key {|key| ... } ⇒ self
Call the block once for each key/value pair in this
Hash, passing the key as a parameter. -
#each_value {|value| ... } ⇒ self
Call the block once for each key/value pair in this
Hash, passing the value as a parameter. -
#empty? ⇒ Boolean
Return
trueif thisHashcontains no key/value pairs. -
#eql?(other) ⇒ Boolean
Return true if
otherhas the same type and contents as thisHash. -
#except(*keys) ⇒ Hash
Return a new
Hashwith the associations for all of the givenkeysremoved. -
#fetch(key, default = Undefined) ⇒ Object
Retrieve the value corresponding to the given key object, or use the provided default value or block, or otherwise raise a
KeyError. -
#find {|key, value| ... } ⇒ Array
(also: #detect)
Yield ‘[key, value]` pairs until one is found for which the block returns true.
-
#flatten(level = 1) ⇒ Vector
Return a new Vector which is a one-dimensional flattening of this
Hash. -
#get(key) ⇒ Object
(also: #[])
Retrieve the value corresponding to the provided key object.
-
#hash ⇒ Integer
See ‘Object#hash`.
-
#initialize(pairs = nil) {|key| ... } ⇒ Hash
constructor
A new instance of Hash.
-
#inspect ⇒ String
Return the contents of this
Hashas a programmer-readableString. -
#invert ⇒ Hash
Return a new
Hashcreated by using keys as values and values as keys. -
#key(value) ⇒ Object
Searches through the
Hash, comparingvaluewith each value (using ‘#==`). -
#key?(key) ⇒ Boolean
(also: #has_key?, #include?, #member?)
Return
trueif the given key object is present in thisHash. -
#keys ⇒ Set
Return a new Set containing the keys from this
Hash. -
#map {|key, value| ... } ⇒ Hash
(also: #collect)
Call the block once for each key/value pair in this
Hash, passing the key/value pair as parameters. - #marshal_dump ⇒ ::Hash
- #marshal_load(dictionary) ⇒ Object
-
#merge(other) {|key, my_value, other_value| ... } ⇒ Hash
Return a new
Hashcontaining all the key/value pairs from thisHashandother. -
#pretty_print(pp) ⇒ Object
Allows this
Hashto be printed at thepryconsole, or usingpp(from the Ruby standard library), in a way which takes the amount of horizontal space on the screen into account, and which indents nested structures to make them easier to read. -
#put(key, value = yield(get(key))) {|value| ... } ⇒ Hash
Return a new
Hashwith the existing key/value associations, plus an association between the provided key and value. -
#rassoc(obj) ⇒ Array
Searches through the
Hash, comparingobjwith each value (using ‘#==`). -
#reverse_each {|key, value| ... } ⇒ self
Call the block once for each key/value pair in this
Hash, passing the key/value pair as parameters. -
#sample ⇒ Array
Return a randomly chosen ‘[key, value]` pair from this
Hash. -
#select {|key, value| ... } ⇒ Hash
(also: #find_all, #keep_if)
Return a new
Hashwith all the key/value pairs for which the block returns true. -
#size ⇒ Integer
(also: #length)
Return the number of key/value pairs in this
Hash. -
#slice(*wanted) ⇒ Hash
Return a new
Hashwith only the associations for thewantedkeys retained. -
#sort ⇒ Vector
Return a sorted Vector which contains all the ‘[key, value]` pairs in this
Hashas two-element `Array`s. -
#sort_by {|key, value| ... } ⇒ Vector
Return a Vector which contains all the ‘[key, value]` pairs in this
Hashas two-element Arrays. -
#store(key, value) ⇒ Hash
An alias for #put to match RubyHash’s API.
-
#to_hash ⇒ ::Hash
(also: #to_h)
Convert this
Immutable::Hashto an instance of Ruby’s built-inHash. -
#update_in(*key_path) {|value| ... } ⇒ Hash
Return a new
Hashwith a deeply nested value modified to the result of the given code block. -
#value?(value) ⇒ Boolean
(also: #has_value?)
Return
trueif thisHashhas one or more keys which map to the provided value. -
#values ⇒ Vector
Return a new Vector populated with the values from this
Hash. -
#values_at(*wanted) ⇒ Vector
Return a Vector of the values which correspond to the
wantedkeys.
Methods included from Enumerable
#<=>, #compact, #each_index, #grep, #group_by, #join, #partition, #product, #reject, #sum, #to_set
Methods included from Enumerable
Constructor Details
Class Method Details
.[](pairs = nil) ⇒ Hash
Create a new Hash populated with the given key/value pairs.
72 73 74 |
# File 'lib/immutable/hash.rb', line 72 def [](pairs = nil) (pairs.nil? || pairs.empty?) ? empty : new(pairs) end |
.alloc(trie = EmptyTrie, block = nil) ⇒ Hash
“Raw” allocation of a new Hash. Used internally to create a new instance quickly after obtaining a modified Trie.
89 90 91 92 93 94 |
# File 'lib/immutable/hash.rb', line 89 def alloc(trie = EmptyTrie, block = nil) obj = allocate obj.instance_variable_set(:@trie, trie) obj.instance_variable_set(:@default, block) obj.freeze end |
.empty ⇒ Hash
Return an empty Hash. If used on a subclass, returns an empty instance of that class.
80 81 82 |
# File 'lib/immutable/hash.rb', line 80 def empty @empty ||= self.new end |
Instance Method Details
#==(other) ⇒ Boolean
Return true if other has the same contents as this Hash. Will convert other to a Ruby Hash using #to_hash if necessary.
733 734 735 |
# File 'lib/immutable/hash.rb', line 733 def ==(other) self.eql?(other) || (other.respond_to?(:to_hash) && to_hash.eql?(other.to_hash)) end |
#assoc(obj) ⇒ Array
Searches through the Hash, comparing obj with each key (using ‘#==`). When a matching key is found, return the `[key, value]` pair as an array. Return nil if no match is found.
661 662 663 664 |
# File 'lib/immutable/hash.rb', line 661 def assoc(obj) each { |entry| return entry if obj == entry[0] } nil end |
#clear ⇒ Hash
Return an empty Hash instance, of the same class as this one. Useful if you have multiple subclasses of Hash and want to treat them polymorphically. Maintains the default block, if there is one.
711 712 713 714 715 716 717 |
# File 'lib/immutable/hash.rb', line 711 def clear if @default self.class.alloc(EmptyTrie, @default) else self.class.empty end end |
#default_proc ⇒ Proc
Return the default block if there is one. Otherwise, return nil.
109 110 111 |
# File 'lib/immutable/hash.rb', line 109 def default_proc @default end |
#delete(key) ⇒ Hash
Return a new Hash with key removed. If key is not present, return self.
322 323 324 |
# File 'lib/immutable/hash.rb', line 322 def delete(key) derive_new_hash(@trie.delete(key)) end |
#dup ⇒ Hash Also known as: clone
Return self. Since this is an immutable object duplicates are equivalent.
765 766 767 |
# File 'lib/immutable/hash.rb', line 765 def dup self end |
#each {|key, value| ... } ⇒ self Also known as: each_pair
Call the block once for each key/value pair in this Hash, passing the key/value pair as parameters. No specific iteration order is guaranteed, though the order will be stable for any particular Hash.
340 341 342 343 344 |
# File 'lib/immutable/hash.rb', line 340 def each(&block) return to_enum if not block_given? @trie.each(&block) self end |
#each_key {|key| ... } ⇒ self
Call the block once for each key/value pair in this Hash, passing the key as a parameter. Ordering guarantees are the same as #each.
379 380 381 382 383 |
# File 'lib/immutable/hash.rb', line 379 def each_key return enum_for(:each_key) if not block_given? @trie.each { |k,v| yield k } self end |
#each_value {|value| ... } ⇒ self
Call the block once for each key/value pair in this Hash, passing the value as a parameter. Ordering guarantees are the same as #each.
398 399 400 401 402 |
# File 'lib/immutable/hash.rb', line 398 def each_value return enum_for(:each_value) if not block_given? @trie.each { |k,v| yield v } self end |
#empty? ⇒ Boolean
Return true if this Hash contains no key/value pairs.
127 128 129 |
# File 'lib/immutable/hash.rb', line 127 def empty? @trie.empty? end |
#eql?(other) ⇒ Boolean
Return true if other has the same type and contents as this Hash.
723 724 725 726 |
# File 'lib/immutable/hash.rb', line 723 def eql?(other) return true if other.equal?(self) instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie)) end |
#except(*keys) ⇒ Hash
Return a new Hash with the associations for all of the given keys removed.
554 555 556 |
# File 'lib/immutable/hash.rb', line 554 def except(*keys) keys.reduce(self) { |hash, key| hash.delete(key) } end |
#fetch(key) ⇒ Object #fetch(key) {|key| ... } ⇒ Object #fetch(key, default) ⇒ Object
Retrieve the value corresponding to the given key object, or use the provided default value or block, or otherwise raise a KeyError.
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/immutable/hash.rb', line 220 def fetch(key, default = Undefined) entry = @trie.get(key) if entry entry[1] elsif block_given? yield(key) elsif default != Undefined default else raise KeyError, "key not found: #{key.inspect}" end end |
#find {|key, value| ... } ⇒ Array Also known as: detect
Yield ‘[key, value]` pairs until one is found for which the block returns true. Return that `[key, value]` pair. If the block never returns true, return nil.
450 451 452 453 454 |
# File 'lib/immutable/hash.rb', line 450 def find return enum_for(:find) unless block_given? each { |entry| return entry if yield entry } nil end |
#flatten(level = 1) ⇒ Vector
Return a new Vector which is a one-dimensional flattening of this Hash. If level is 1, all the ‘[key, value]` pairs in the hash will be concatenated into one Vector. If level is greater than 1, keys or values which are themselves `Array`s or Vectors will be recursively flattened into the output Vector. The depth to which that flattening will be recursively applied is determined by level.
As a special case, if level is 0, each ‘[key, value]` pair will be a separate element in the returned Vector.
644 645 646 647 648 649 650 |
# File 'lib/immutable/hash.rb', line 644 def flatten(level = 1) return Vector.new(self) if level == 0 array = [] each { |k,v| array << k; array << v } array.flatten!(level-1) if level > 1 Vector.new(array.freeze) end |
#get(key) ⇒ Object Also known as: []
Retrieve the value corresponding to the provided key object. If not found, and this Hash has a default block, the default block is called to provide the value. Otherwise, return nil.
177 178 179 180 181 182 183 184 |
# File 'lib/immutable/hash.rb', line 177 def get(key) entry = @trie.get(key) if entry entry[1] elsif @default @default.call(key) end end |
#hash ⇒ Integer
See ‘Object#hash`.
739 740 741 742 743 |
# File 'lib/immutable/hash.rb', line 739 def hash keys.to_a.sort.reduce(0) do |hash, key| (hash << 32) - hash + key.hash + get(key).hash end end |
#inspect ⇒ String
Return the contents of this Hash as a programmer-readable String. If all the keys and values are serializable as Ruby literal strings, the returned string can be passed to eval to reconstitute an equivalent Hash. The default block (if there is one) will be lost when doing this, however.
751 752 753 754 755 756 757 758 759 760 |
# File 'lib/immutable/hash.rb', line 751 def inspect result = "#{self.class}[" i = 0 each do |key, val| result << ', ' if i > 0 result << key.inspect << ' => ' << val.inspect i += 1 end result << "]" end |
#invert ⇒ Hash
Return a new Hash created by using keys as values and values as keys. If there are multiple values which are equivalent (as determined by #hash and #eql?), only one out of each group of equivalent values will be retained. Which one specifically is undefined.
619 620 621 622 623 |
# File 'lib/immutable/hash.rb', line 619 def invert pairs = [] each { |k,v| pairs << [v, k] } self.class.new(pairs, &@default) end |
#key(value) ⇒ Object
Searches through the Hash, comparing value with each value (using ‘#==`). When a matching value is found, return its associated key object. Return nil if no match is found.
689 690 691 692 |
# File 'lib/immutable/hash.rb', line 689 def key(value) each { |entry| return entry[0] if value == entry[1] } nil end |
#key?(key) ⇒ Boolean Also known as: has_key?, include?, member?
Return true if the given key object is present in this Hash. More precisely, return true if a key with the same #hash code, and which is also #eql? to the given key object is present.
140 141 142 |
# File 'lib/immutable/hash.rb', line 140 def key?(key) @trie.key?(key) end |
#keys ⇒ Set
Return a new Set containing the keys from this Hash.
594 595 596 |
# File 'lib/immutable/hash.rb', line 594 def keys Set.alloc(@trie) end |
#map {|key, value| ... } ⇒ Hash Also known as: collect
Call the block once for each key/value pair in this Hash, passing the key/value pair as parameters. The block should return a ‘[key, value]` array each time. All the returned `[key, value]` arrays will be gathered into a new Hash.
415 416 417 418 419 |
# File 'lib/immutable/hash.rb', line 415 def map return enum_for(:map) unless block_given? return self if empty? self.class.new(super, &@default) end |
#marshal_dump ⇒ ::Hash
806 807 808 |
# File 'lib/immutable/hash.rb', line 806 def marshal_dump to_hash end |
#marshal_load(dictionary) ⇒ Object
811 812 813 |
# File 'lib/immutable/hash.rb', line 811 def marshal_load(dictionary) @trie = Trie[dictionary] end |
#merge(other) {|key, my_value, other_value| ... } ⇒ Hash
Return a new Hash containing all the key/value pairs from this Hash and other. If no block is provided, the value for entries with colliding keys will be that from other. Otherwise, the value for each duplicate key is determined by calling the block.
other can be an Immutable::Hash, a built-in Ruby Hash, or any Enumerable object which yields ‘[key, value]` pairs.
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'lib/immutable/hash.rb', line 479 def merge(other) trie = if block_given? other.reduce(@trie) do |trie, (key, value)| if entry = trie.get(key) trie.put(key, yield(key, entry[1], value)) else trie.put(key, value) end end else @trie.bulk_put(other) end derive_new_hash(trie) end |
#pretty_print(pp) ⇒ Object
Allows this Hash to be printed at the pry console, or using pp (from the Ruby standard library), in a way which takes the amount of horizontal space on the screen into account, and which indents nested structures to make them easier to read.
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 |
# File 'lib/immutable/hash.rb', line 776 def pretty_print(pp) pp.group(1, "#{self.class}[", "]") do pp.breakable '' pp.seplist(self, nil) do |key, val| pp.group do key.pretty_print(pp) pp.text ' => ' pp.group(1) do pp.breakable '' val.pretty_print(pp) end end end end end |
#put(key, value = yield(get(key))) {|value| ... } ⇒ Hash
Return a new Hash with the existing key/value associations, plus an association between the provided key and value. If an equivalent key is already present, its associated value will be replaced with the provided one.
If the value argument is missing, but an optional code block is provided, it will be passed the existing value (or nil if there is none) and what it returns will replace the existing value. This is useful for “transforming” the value associated with a certain key.
Avoid mutating objects which are used as keys. ‘String`s are an exception: unfrozen `String`s which are used as keys are internally duplicated and frozen. This matches RubyHash’s behaviour.
258 259 260 261 262 263 264 265 |
# File 'lib/immutable/hash.rb', line 258 def put(key, value = yield(get(key))) new_trie = @trie.put(key, value) if new_trie.equal?(@trie) self else self.class.alloc(new_trie, @default) end end |
#rassoc(obj) ⇒ Array
Searches through the Hash, comparing obj with each value (using ‘#==`). When a matching value is found, return the `[key, value]` pair as an array. Return nil if no match is found.
675 676 677 678 |
# File 'lib/immutable/hash.rb', line 675 def rassoc(obj) each { |entry| return entry if obj == entry[1] } nil end |
#reverse_each {|key, value| ... } ⇒ self
Call the block once for each key/value pair in this Hash, passing the key/value pair as parameters. Iteration order will be the opposite of #each.
360 361 362 363 364 |
# File 'lib/immutable/hash.rb', line 360 def reverse_each(&block) return enum_for(:reverse_each) if not block_given? @trie.reverse_each(&block) self end |
#sample ⇒ Array
Return a randomly chosen ‘[key, value]` pair from this Hash. If the hash is empty, return nil.
702 703 704 |
# File 'lib/immutable/hash.rb', line 702 def sample @trie.at(rand(size)) end |
#select {|key, value| ... } ⇒ Hash Also known as: find_all, keep_if
Return a new Hash with all the key/value pairs for which the block returns true.
432 433 434 435 |
# File 'lib/immutable/hash.rb', line 432 def select(&block) return enum_for(:select) unless block_given? derive_new_hash(@trie.select(&block)) end |
#size ⇒ Integer Also known as: length
Return the number of key/value pairs in this Hash.
119 120 121 |
# File 'lib/immutable/hash.rb', line 119 def size @trie.size end |
#slice(*wanted) ⇒ Hash
Return a new Hash with only the associations for the wanted keys retained.
566 567 568 569 570 |
# File 'lib/immutable/hash.rb', line 566 def slice(*wanted) trie = Trie.new(0) wanted.each { |key| trie.put!(key, get(key)) if key?(key) } self.class.alloc(trie, @default) end |
#sort ⇒ Vector #sort({ |(k1, v1), (k2, v2)| ... }) {|(k1, v1), (k2, v2)| ... } ⇒ Vector
Return a sorted Vector which contains all the ‘[key, value]` pairs in this Hash as two-element `Array`s.
523 524 525 |
# File 'lib/immutable/hash.rb', line 523 def sort Vector.new(super) end |
#sort_by {|key, value| ... } ⇒ Vector
Return a Vector which contains all the ‘[key, value]` pairs in this Hash as two-element Arrays. The order which the pairs will appear in is determined by passing each pair to the code block to obtain a sort key object, and comparing the sort keys using `#<=>`.
542 543 544 |
# File 'lib/immutable/hash.rb', line 542 def sort_by Vector.new(super) end |
#store(key, value) ⇒ Hash
309 310 311 |
# File 'lib/immutable/hash.rb', line 309 def store(key, value) put(key, value) end |
#to_hash ⇒ ::Hash Also known as: to_h
Convert this Immutable::Hash to an instance of Ruby’s built-in Hash.
795 796 797 798 799 800 801 |
# File 'lib/immutable/hash.rb', line 795 def to_hash output = {} each do |key, value| output[key] = value end output end |
#update_in(*key_path) {|value| ... } ⇒ Hash
Return a new Hash with a deeply nested value modified to the result of the given code block. When traversing the nested ‘Hash`es and `Vector`s, non-existing keys are created with empty Hash values.
The code block receives the existing value of the deeply nested key (or nil if it doesn’t exist). This is useful for “transforming” the value associated with a certain key.
Note that the original Hash and sub-‘Hash`es and sub-`Vector`s are left unmodified; new data structure copies are created along the path wherever needed.
288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/immutable/hash.rb', line 288 def update_in(*key_path, &block) if key_path.empty? raise ArgumentError, "must have at least one key in path" end key = key_path[0] if key_path.size == 1 new_value = block.call(get(key)) else value = fetch(key, EmptyHash) new_value = value.update_in(*key_path[1..-1], &block) end put(key, new_value) end |
#value?(value) ⇒ Boolean Also known as: has_value?
Return true if this Hash has one or more keys which map to the provided value.
154 155 156 157 |
# File 'lib/immutable/hash.rb', line 154 def value?(value) each { |k,v| return true if value == v } false end |
#values ⇒ Vector
Return a new Vector populated with the values from this Hash.
605 606 607 |
# File 'lib/immutable/hash.rb', line 605 def values Vector.new(each_value.to_a.freeze) end |
#values_at(*wanted) ⇒ Vector
Return a Vector of the values which correspond to the wanted keys. If any of the wanted keys are not present in this Hash, they will be skipped.
581 582 583 584 585 |
# File 'lib/immutable/hash.rb', line 581 def values_at(*wanted) array = [] wanted.each { |key| array << get(key) if key?(key) } Vector.new(array.freeze) end |