Module: Gorillib::Hashlike

Includes:
Serialization
Defined in:
lib/gorillib/hashlike.rb,
lib/gorillib/hashlike/keys.rb,
lib/gorillib/hashlike/slice.rb,
lib/gorillib/hashlike/compact.rb,
lib/gorillib/hashlike/deep_dup.rb,
lib/gorillib/hashlike/deep_hash.rb,
lib/gorillib/hashlike/deep_merge.rb,
lib/gorillib/hashlike/deep_compact.rb,
lib/gorillib/serialization/to_wire.rb,
lib/gorillib/serialization/to_wire.rb,
lib/gorillib/hashlike/reverse_merge.rb,
lib/gorillib/hashlike/hashlike_via_accessors.rb

Overview

Your class must provide #[], #[]=, #delete, and #keys --

  • hsh[key] Element Reference -- Retrieves the value stored for +key+.
  • hsh[key] = val Element Assignment -- Associates +val+ with +key+.
  • hsh.delete(key) Deletes & returns the value whose key is equal to +key+.
  • hsh.keys Returns a new array populated with the keys.

(see Hashlike::HashlikeViaAccessors for example)

Given the above, hashlike will provide the rest, defining the methods

:each_pair, :each, :each_key, :each_value, :values_at, :values_of, :values,
:size, :length, :has_key?, :include?, :key?, :member?, :has_value?, :value?,
:fetch, :key, :assoc, :rassoc, :empty?, :merge, :update, :merge!, :reject!,
:select!, :delete_if, :keep_if, :reject, :clear, :store, :to_hash, :invert,
:flatten

and these methods added by Enumerable:

:each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
:entries, :to_a, :map, :collect, :collect_concat, :group_by, :flat_map,
:inject, :reduce, :chunk, :reverse_each, :slice_before, :drop, :drop_while,
:take, :take_while, :detect, :find, :find_all, :select, :find_index, :grep,
:all?, :any?, :none?, :one?, :first, :count, :zip, :max, :max_by, :min,
:min_by, :minmax, :minmax_by, :sort, :sort_by, :cycle, :partition,

It does not define these methods that do exist on hash:

:default, :default=, :default_proc, :default_proc=,
:compare_by_identity, :compare_by_identity?,
:replace, :rehash, :shift

=== Chinese wall

With a few exceptions, all methods are defined only in terms of

#[], #[]=, #delete, #keys, #each_pair and #has_key?

(exceptions: merge family depend on #update; the reject/select/xx_if family depend on each other; #invert & #flatten call #to_hash; #rassoc calls #key)

=== custom iterators

Hashlike typically defines the following fundamental iterators by including Gorillib::Hashlike::EnumerateFromKeys:

:each_pair, :each, :values, :values_at, :length

However, if the #each_pair method already exists on the class (as it does for Struct), those methods will not be defined. The class is held responsible for the implementation of all five. (Of these, #each_pair is the only method called from elsewhere in Hashlike, while #each is the only method called from Enumerable).

=== #convert_key (Indifferent Access)

If you define #convert_key the #values_at, #has_key?, #fetch, and #assoc methods will use it to sanitize keys coming in from the outside. It's assumed that you will do the same with #[], #[]= and #delete. (see Gorillib::HashWithIndifferentAccess for an example).

Defined Under Namespace

Modules: Compact, DeepCompact, DeepDup, DeepHash, DeepMerge, EnumerateFromKeys, HashlikeViaAccessors, Keys, OverrideEnumerable, ReverseMerge, Serialization, Slice

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Serialization

#to_wire

Class Method Details

.included(base) ⇒ Object


806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
# File 'lib/gorillib/hashlike.rb', line 806

def self.included(base)
  base.class_eval do
    include EnumerateFromKeys unless method_defined?(:each_pair)
    unless include?(Enumerable)
      include Enumerable
      include OverrideEnumerable
    end

    # included here so they win out over Enumerable
    alias_method :include?, :has_key?
    alias_method :key?,     :has_key?
    alias_method :member?,  :has_key?
    alias_method :value?,   :has_value?
    alias_method :merge!,   :update
    alias_method :size,     :length
  end
end

Instance Method Details

#assoc(key) ⇒ Array?

Searches through the hashlike comparing obj with the key using ==. Returns the key-value pair (two elements array) or nil if no match is found.

Examples:

hsh = { "colors"  => ["red", "blue", "green"],
        "letters" => [:a, :b, :c ]}
hsh.assoc("letters")  # => ["letters", [:a, :b, :c]]
hsh.assoc("foo")      # => nil

Returns:

  • (Array, nil)

    the key-value pair (two elements array) or nil if no match is found.

See Also:

  • Array#assoc.

427
428
429
430
431
# File 'lib/gorillib/hashlike.rb', line 427

def assoc(key)
  key = convert_key(key) if respond_to?(:convert_key)
  return unless has_key?(key)
  [key, self[key]]
end

#clearHashlike

Removes all key-value pairs from +hsh+.

Examples:

hsh = { :a => 100, :b => 200 }   # => { :a => 100, :b => 200 }
hsh.clear                        # => {}

Returns:


730
731
732
# File 'lib/gorillib/hashlike.rb', line 730

def clear
  each_pair{|k,v| delete(k) }
end

#hsh.delete_ifHashlike #hsh.delete_if(->an_enumerator) ⇒ Enumerator

Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.

If no block is given, an enumerator is returned instead.

Examples:

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.delete_if{|key, val| key.to_s >= "b" }   # => { :a => 100 }

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.delete_if{|key, val| key.to_s >= "z" }   # => { :a => 100, :b => 200, :c => 300 }

Overloads:

  • #hsh.delete_ifHashlike

    Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.

    Returns:

  • #hsh.delete_if(->an_enumerator) ⇒ Enumerator

    with no block, returns a raw enumerator

    Returns:

    • (Enumerator)

625
626
627
628
629
# File 'lib/gorillib/hashlike.rb', line 625

def delete_if(&block)
  return enum_for(:delete_if) unless block_given?
  reject!(&block)
  self
end

#hsh.each_key {|key| ... } ⇒ Hashlike #hsh.each_key(->an_enumerator) ⇒ Enumerator

Hashlike#each_key

Calls +block+ once for each key in +hsh+, passing the key as a parameter.

If no block is given, an enumerator is returned instead.

Examples:

hsh = { :a => 100, :b => 200 }
hsh.each_key{|key| puts key }
# produces:
a
b

with block arity:

hsh = {[:a,:b] => 3, [:c, :d] => 4, :e => 5}
seen_args = []
hsh.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
# => [[:a, :b, nil], [:c, :d, nil], [:e, nil, nil]]

seen_args = []
hsh.each_key{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
# => [[:a, nil, :b], [:c, nil, :d], [:e, nil, nil]]

Overloads:

  • #hsh.each_key {|key| ... } ⇒ Hashlike

    Calls +block+ once for each key in +hsh+

    Yields:

    • (key)

      in order, each key

    Returns:

  • #hsh.each_key(->an_enumerator) ⇒ Enumerator

    with no block, returns a raw enumerator

    Returns:

    • (Enumerator)

250
251
252
253
254
# File 'lib/gorillib/hashlike.rb', line 250

def each_key
  return enum_for(:each_key) unless block_given?
  each_pair{|k,v| yield k }
  self
end

#hsh.each_value {|val| ... } ⇒ Hashlike #hsh.each_value(->an_enumerator) ⇒ Enumerator

Calls +block+ once for each key in +hsh+, passing the value as a parameter.

If no block is given, an enumerator is returned instead.

Examples:

hsh = { :a => 100, :b => 200 }
hsh.each_value{|value| puts value }
# produces:
100
200

with block arity:

hsh = {:a => [300,333], :b => [400,444], :e => 500})
seen_args = []
hsh.each_value{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
# => [[300, 333, nil], [400, 444, nil], [500, nil, nil]]

seen_args = []
hsh.each_value{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
# => [[300, nil, 333], [400, nil, 444], [500, nil, nil]]

Overloads:

  • #hsh.each_value {|val| ... } ⇒ Hashlike

    Calls +block+ once for each value in +hsh+

    Yields:

    • (val)

      in order by its key, each value

    Returns:

  • #hsh.each_value(->an_enumerator) ⇒ Enumerator

    with no block, returns a raw enumerator

    Returns:

    • (Enumerator)

287
288
289
290
291
# File 'lib/gorillib/hashlike.rb', line 287

def each_value
  return enum_for(:each_value) unless block_given?
  each_pair{|k,v| yield v }
  self
end

#empty?true, false

Returns true if the hashlike contains no key-value pairs, false otherwise.

Examples:

{}.empty?   # => true

Returns:

  • (true, false)

    true if +hsh+ contains no key-value pairs, false otherwise


461
462
463
# File 'lib/gorillib/hashlike.rb', line 461

def empty?
  keys.empty?
end

#fetch(key, default = nil) {|key| ... } ⇒ Object

Returns a value from the hashlike for the given key. If the key can't be found, there are several options:

  • With no other arguments, it will raise a +KeyError+ exception;
  • if default is given, then that will be returned;
  • if the optional code block is specified, then that will be run and its result returned.

Examples:

hsh = { :a => 100, :b => 200 }
hsh.fetch(:a)                          # => 100
hsh.fetch(:z, "go fish")               # => "go fish"
hsh.fetch(:z){|el| "go fish, #{el}"}   # => "go fish, z"

An exception is raised if the key is not found and a default value is not supplied.

hsh = { :a => 100, :b => 200 }
hsh.fetch(:z)
# produces:
prog.rb:2:in `fetch': key not found (KeyError) from prog.rb:2

hsh.fetch(:z, 3)
# => 3

hsh.fetch(:z){|key| key.to_s * 5 }
# => "zzzzz"

Parameters:

  • key (Object)

    the key to query

  • default (Object) (defaults to: nil)

    the value to use if the key is missing

Yields:

  • (key)

    if missing, block called with the key requested

Returns:

  • (Object)

    the value; if missing, the default; if missing, the block's return value

Raises:

  • (KeyError)

    raised if missing, and neither +default+ nor +block+ is supplied


382
383
384
385
386
387
388
389
390
# File 'lib/gorillib/hashlike.rb', line 382

def fetch(key, default=nil, &block)
  key = convert_key(key) if respond_to?(:convert_key)
  warn "#{caller[0]}: warning: block supersedes default value argument" if default && block_given?
  if    has_key?(key) then self[key]
  elsif block_given?  then yield(key)
  elsif default       then default
  else  raise KeyError, "key not found: #{key.inspect}"
  end
end

#flattenArray #flatten(level) ⇒ Array

Returns a new array that is a one-dimensional flattening of this hashlike. That is, for every key or value that is an array, extract its elements into the new array. Unlike Array#flatten, this method does not flatten recursively by default; pass +nil+ explicitly to flatten recursively. The optional level argument determines the level of recursion to flatten.

Examples:

hsh =  {1=> "one", 2 => [2,"two"], 3 => "three"}
hsh.flatten    # => [1, "one", 2, [2, "two"], 3, "three"]
hsh.flatten(2) # => [1, "one", 2, 2, "two", 3, "three"]

with deep nesting

hsh = { [1, 2, [3, 4]] => [1, [2, 3, [4, 5, 6]]] }
hsh.flatten
# =>   [[1, 2, [3, 4]],   [1, [2, 3, [4, 5, 6]]]]
hsh.flatten(0)
# =>  [[[1, 2, [3, 4]],   [1, [2, 3, [4, 5, 6]]]]]
hsh.flatten(1)
# =>   [[1, 2, [3, 4]],   [1, [2, 3, [4, 5, 6]]]]
hsh.flatten(2)
# =>    [1, 2, [3, 4],     1, [2, 3, [4, 5, 6]]]
hsh.flatten(3)
# =>    [1, 2,  3, 4,      1,  2, 3, [4, 5, 6]]
hsh.flatten(4)
# =>    [1, 2,  3, 4,      1,  2, 3,  4, 5, 6]
hsh.flatten.flatten
# =>    [1, 2,  3, 4,      1,  2, 3,  4, 5, 6]

nil level means complete flattening

hsh.flatten(nil)
# =>    [1, 2,  3, 4,      1,  2, 3,  4, 5, 6]

Overloads:

  • #flatten(level) ⇒ Array

    Parameters:

    • level (Integer)

      the level of recursion to flatten, 0 by default.

Returns:

  • (Array)

    the flattened key-value array.


802
803
804
# File 'lib/gorillib/hashlike.rb', line 802

def flatten(*args)
  to_hash.flatten(*args)
end

#has_key?(key) ⇒ true, false

Returns true if the given key is present in +hsh+.

Examples:

hsh = { :a => 100, :b => 200 }
hsh.has_key?(:a)   # => true
hsh.has_key?(:z)   # => false

Parameters:

  • key (Object)

    the key to check for.

Returns:

  • (true, false)

    true if the key is present, false otherwise


328
329
330
331
# File 'lib/gorillib/hashlike.rb', line 328

def has_key?(key)
  key = convert_key(key) if respond_to?(:convert_key)
  keys.include?(key)
end

#has_value?(target) ⇒ true, false

Returns true if the given value is present for some key in +hsh+.

Examples:

hsh = { :a => 100, :b => 200 }
hsh.has_value?(100)   # => true
hsh.has_value?(999)   # => false

Parameters:

  • target (Object)

    the value to query

Returns:

  • (true, false)

    true if the value is present, false otherwise


344
345
346
347
348
# File 'lib/gorillib/hashlike.rb', line 344

def has_value?(target)
  # don't refactor this to any? -- Struct's #any is weird
  each_pair{|key, val| return true if (val == target) }
  false
end

#invertHash

Returns a new hash created by using +hsh+'s values as keys, and the keys as values. If +hsh+ has duplicate values, the result will contain only one of them as a key -- which one is not predictable.

Examples:

hsh = { :n => 100, :m => 100, :y => 300, :d => 200, :a => 0 }
hsh.invert # => { 0 => :a, 100 => :m, 200 => :d, 300 => :y }

Returns:

  • (Hash)

    a new hash, with values for keys and vice-versa


759
760
761
# File 'lib/gorillib/hashlike.rb', line 759

def invert
  to_hash.invert
end

#hsh.keep_ifHashlike #hsh.keep_if(->an_enumerator) ⇒ Enumerator

Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.

If no block is given, an enumerator is returned instead.

Examples:

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.keep_if{|key, val| key.to_s >= "b" }   # => { :b => 200, :c => 300 }

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.keep_if{|key, val| key.to_s >= "a" }   # => { :a => 100, :b => 200, :c => 300 }

Overloads:

  • #hsh.keep_ifHashlike

    Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.

    Returns:

  • #hsh.keep_if(->an_enumerator) ⇒ Enumerator

    with no block, returns a raw enumerator

    Returns:

    • (Enumerator)

651
652
653
654
655
# File 'lib/gorillib/hashlike.rb', line 651

def keep_if(&block)
  return enum_for(:keep_if) unless block_given?
  select!(&block)
  self
end

#key(val) ⇒ Object?

Searches the hash for an entry whose value == +val+, returning the corresponding key. If not found, returns +nil+.

You are guaranteed that the first matching key in #keys will be the one returned.

Examples:

hsh = { :a => 100, :b => 200 }
hsh.key(200)   # => :b
hsh.key(999)   # => nil

Parameters:

  • val (Object)

    the value to look up

Returns:

  • (Object, nil)

    the key for the given val, or nil if missing


407
408
409
# File 'lib/gorillib/hashlike.rb', line 407

def key(val)
  keys.find{|key| self[key] == val }
end

#hsh.merge(other_hash) ⇒ Hashlike #hsh.merge(other_hash) {|Object, Object, Object| ... } ⇒ Hashlike

Returns a new hashlike containing the contents of +other_hash+ and the contents of +hsh+. If no block is specified, the value for entries with duplicate keys will be that of +other_hash+. Otherwise the value for each duplicate key is determined by calling the block with the key, its value in +hsh+ and its value in +other_hash+.

Examples:

h1 = { :a => 100, :b => 200 }
h2 = { :b => 254, :c => 300 }
h1.merge(h2)
# => { :a=>100, :b=>254, :c=>300 }
h1.merge(h2){|key, oldval, newval| newval - oldval}
# => { :a => 100, :b => 54,  :c => 300 }
h1
# => { :a => 100, :b => 200 }

Overloads:

  • #hsh.merge(other_hash) ⇒ Hashlike

    Adds the contents of +other_hash+ to +hsh+. Entries with duplicate keys are overwritten with the values from +other_hash+

    Parameters:

    • other_hash (Hash, Hashlike)

      the hash to merge (it wins)

    Returns:

  • #hsh.merge(other_hash) {|Object, Object, Object| ... } ⇒ Hashlike

    Adds the contents of +other_hash+ to +hsh+. The value of each duplicate key is determined by calling the block with the key, its value in +hsh+ and its value in +other_hash+.

    Parameters:

    • other_hash (Hash, Hashlike)

      the hash to merge (it wins)

    Yields:

    Returns:


539
540
541
# File 'lib/gorillib/hashlike.rb', line 539

def merge(*args, &block)
  self.dup.update(*args, &block)
end

#rassoc(val) ⇒ Array?

Searches through the hashlike comparing obj with the value using ==. Returns the first key-value pair (two-element array) that matches, or nil if no match is found.

Examples:

hsh = { 1 => "one", 2 => "two", 3 => "three", "ii" => "two"}
hsh.rassoc("two")    # => [2, "two"]
hsh.rassoc("four")   # => nil

Returns:

  • (Array, nil)

    The first key-value pair (two-element array) that matches, or nil if no match is found

See Also:

  • Array#rassoc.

448
449
450
451
# File 'lib/gorillib/hashlike.rb', line 448

def rassoc(val)
  key = key(val) or return
  [key, self[key]]
end

#hsh.reject!Hashlike? #hsh.reject!(->an_enumerator) ⇒ Enumerator

Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy (equivalent to Hashlike#delete_if), but returns nil if no changes were made.

Examples:

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.delete_if{|key, val| key.to_s >= "b" }   # => { :a => 100 }

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.delete_if{|key, val| key.to_s >= "z" }   # nil

Overloads:

  • #hsh.reject!Hashlike?

    Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.

    Returns:

  • #hsh.reject!(->an_enumerator) ⇒ Enumerator

    with no block, returns a raw enumerator

    Returns:

    • (Enumerator)

562
563
564
565
566
567
568
569
570
571
572
# File 'lib/gorillib/hashlike.rb', line 562

def reject!(&block)
  return enum_for(:reject!) unless block_given?
  changed = false
  each_pair do |key, val|
    if yield(*[key, val].take(block.arity))
      changed = true
      delete(key)
    end
  end
  changed ? self : nil
end

#hsh.select!Hashlike #hsh.select!(->an_enumerator) ⇒ Enumerator

Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy (equivalent to Hashlike#keep_if), but returns nil if no changes were made.

Examples:

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.select!{|key, val| key.to_s >= "b" }   # => { :b => 200, :c => 300 }

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.select!{|key, val| key.to_s >= "a" }   # => { :a => 100, :b => 200, :c => 300 }

Overloads:

  • #hsh.select!Hashlike

    Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.

    Returns:

  • #hsh.select!(->an_enumerator) ⇒ Enumerator

    with no block, returns a raw enumerator

    Returns:

    • (Enumerator)

593
594
595
596
597
598
599
600
601
602
603
# File 'lib/gorillib/hashlike.rb', line 593

def select!(&block)
  return enum_for(:select!) unless block_given?
  changed = false
  each_pair do |key, val|
    if not yield(*[key, val].take(block.arity))
      changed = true
      delete(key)
    end
  end
  changed ? self : nil
end

#store(key, val) ⇒ Object

alias for #[]=


214
215
216
# File 'lib/gorillib/hashlike.rb', line 214

def store(key, val)
  self[key] = val
end

#to_hashHash

Returns a hash with each key set to its associated value.

Examples:

my_hshlike = MyHashlike.new
my_hshlike[:a] = 100; my_hshlike[:b] = 200
my_hshlike.to_hash # => { :a => 100, :b => 200 }

Returns:

  • (Hash)

    a new Hash instance, with each key set to its associated value.


744
745
746
# File 'lib/gorillib/hashlike.rb', line 744

def to_hash
  {}.tap{|hsh| each_pair{|key, val| hsh[key] = val } }
end

#hsh.update(other_hash) ⇒ Hashlike #hsh.update(other_hash) {|Object, Object, Object| ... } ⇒ Hashlike

Adds the contents of +other_hash+ to +hsh+. If no block is specified, entries with duplicate keys are overwritten with the values from +other_hash+, otherwise the value of each duplicate key is determined by calling the block with the key, its value in +hsh+ and its value in +other_hash+.

Examples:

h1 = { :a => 100, :b => 200 }
h2 = { :b => 254, :c => 300 }
h1.merge!(h2)
# => { :a => 100, :b => 254, :c => 300 }

h1 = { :a => 100, :b => 200 }
h2 = { :b => 254, :c => 300 }
h1.merge!(h2){|key, v1, v2| v1 }
# => { :a => 100, :b => 200, :c => 300 }

Overloads:

  • #hsh.update(other_hash) ⇒ Hashlike

    Adds the contents of +other_hash+ to +hsh+. Entries with duplicate keys are overwritten with the values from +other_hash+

    Parameters:

    • other_hash (Hash, Hashlike)

      the hash to merge (it wins)

    Returns:

  • #hsh.update(other_hash) {|Object, Object, Object| ... } ⇒ Hashlike

    Adds the contents of +other_hash+ to +hsh+. The value of each duplicate key is determined by calling the block with the key, its value in +hsh+ and its value in +other_hash+.

    Parameters:

    • other_hash (Hash, Hashlike)

      the hash to merge (it wins)

    Yields:

    Returns:

Raises:

  • (TypeError)

497
498
499
500
501
502
503
504
505
506
# File 'lib/gorillib/hashlike.rb', line 497

def update(other_hash)
  raise TypeError, "can't convert #{other_hash.nil? ? 'nil' : other_hash.class} into Hash" unless other_hash.respond_to?(:each_pair)
  other_hash.each_pair do |key, val|
    if block_given? && has_key?(key)
      val = yield(key, val, self[key])
    end
    self[key] = val
  end
  self
end

#values_of(*allowed_keys) ⇒ Array

Array containing the values associated with the given keys.

Examples:

hsh = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
hsh.values_of("cow", "cat")  # => ["bovine", "feline"]
hsh = { :a => 100, :b => 200, :c => 300 }
hsh.values_of(:c, :a, :c, :z, :a)
# => [300, 100, 300, nil, 100]

Parameters:

  • allowed_keys (Object)

    the keys to retrieve.

Returns:

  • (Array)

    the values, in order according to allowed_keys.

See Also:

  • Hashlike#select.

310
311
312
313
314
315
# File 'lib/gorillib/hashlike.rb', line 310

def values_of(*allowed_keys)
  allowed_keys.map do |key|
    key = convert_key(key) if respond_to?(:convert_key)
    self[key] if has_key?(key)
  end
end