module Gorillib::Hashlike

Your class must provide [], []=, delete, and 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).

Public Class Methods

included(base) click to toggle source
# 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

Public Instance Methods

assoc(key) click to toggle source

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.

@see Array#assoc.

@example

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

@return [Array, nil] the key-value pair (two elements array) or nil if no

match is found.
# 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
clear() click to toggle source

Removes all key-value pairs from hsh.

@example

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

@return [Hashlike] this hashlike, emptied

# File lib/gorillib/hashlike.rb, line 730
def clear
  each_pair{|k,v| delete(k) }
end
delete_if(&block) click to toggle source

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

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

@example

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 }

@overload hsh.delete_if{|key, val| block } -> hsh

Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
@return [Hashlike]

@overload hsh.delete_if -> an_enumerator

with no block, returns a raw enumerator
@return [Enumerator]
# File lib/gorillib/hashlike.rb, line 625
def delete_if(&block)
  return enum_for(:delete_if) unless block_given?
  reject!(&block)
  self
end
each_key() { |k| ... } click to toggle source

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.

@example

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

@example 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]]

@overload hsh.each_key{|key| block } -> hsh

Calls +block+ once for each key in +hsh+
@yield [key] in order, each key
@return [Hashlike]

@overload hsh.each_key -> an_enumerator

with no block, returns a raw enumerator
@return [Enumerator]
# 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
each_value() { |v| ... } click to toggle source

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

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

@example

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

@example 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]]

@overload hsh.each_value{|val| block } -> hsh

Calls +block+ once for each value in +hsh+
@yield [val] in order by its key, each value
@return [Hashlike]

@overload hsh.each_value -> an_enumerator

with no block, returns a raw enumerator
@return [Enumerator]
# 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?() click to toggle source

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

@example

{}.empty?   # => true

@return [true, false] true if hsh contains no key-value pairs, false otherwise

# File lib/gorillib/hashlike.rb, line 461
def empty?
  keys.empty?
end
fetch(key, default=nil) { |key| ... } click to toggle source

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.

@example

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"

@example 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"

@param key [Object] the key to query @param default [Object] the value to use if the key is missing @raise [KeyError] raised if missing, and neither default nor block is supplied @yield [key] if missing, block called with the key requested @return [Object] the value; if missing, the default; if missing, the

block's return value
# 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
flatten(*args) click to toggle source

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.

@example

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"]

@example 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]

@example nil level means complete flattening

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

@overload flatten @overload flatten(level)

@param  level [Integer] the level of recursion to flatten, 0 by default.

@return [Array] the flattened key-value array.

# File lib/gorillib/hashlike.rb, line 802
def flatten(*args)
  to_hash.flatten(*args)
end
has_key?(key) click to toggle source

Returns true if the given key is present in hsh.

@example

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

@param key [Object] the key to check for. @return [true, false] true if the key is present, false otherwise

# 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) click to toggle source

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

@example

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

@param target [Object] the value to query @return [true, false] true if the value is present, false otherwise

# 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
invert() click to toggle source

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.

@example

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

@return [Hash] a new hash, with values for keys and vice-versa

# File lib/gorillib/hashlike.rb, line 759
def invert
  to_hash.invert
end
keep_if(&block) click to toggle source

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

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

@example

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 }

@overload hsh.keep_if{|key, val| block } -> hsh

Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.
@return [Hashlike]

@overload hsh.keep_if -> an_enumerator

with no block, returns a raw enumerator
@return [Enumerator]
# 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) click to toggle source

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.

@example

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

@param val [Object] the value to look up @return [Object, nil] the key for the given val, or nil if missing

# File lib/gorillib/hashlike.rb, line 407
def key(val)
  keys.find{|key| self[key] == val }
end
merge(*args, &block) click to toggle source

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.

@example

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 }

@overload hsh.merge(other_hash) -> hsh

Adds the contents of +other_hash+ to +hsh+.  Entries with duplicate keys are
overwritten with the values from +other_hash+
@param  other_hash [Hash, Hashlike] the hash to merge (it wins)
@return [Hashlike] a new merged hashlike

@overload hsh.merge(other_hash){|key, oldval, newval| block} -> hsh

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+.
@param  other_hash [Hash, Hashlike] the hash to merge (it wins)
@yield  [Object, Object, Object] called if key exists in each +hsh+
@return [Hashlike] a new merged hashlike
# File lib/gorillib/hashlike.rb, line 539
def merge(*args, &block)
  self.dup.update(*args, &block)
end
rassoc(val) click to toggle source

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.

@see Array#rassoc.

@example

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

@return [Array, nil] The first key-value pair (two-element array) that

matches, or nil if no match is found
# File lib/gorillib/hashlike.rb, line 448
def rassoc(val)
  key = key(val) or return
  [key, self[key]]
end
reject!() { |*[key, val].take(arity)| ... } click to toggle source

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.

@example

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

@overload hsh.reject!{|key, val| block } -> hsh or nil

Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
@return [Hashlike, nil]

@overload hsh.reject! -> an_enumerator

with no block, returns a raw enumerator
@return [Enumerator]
# 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
select!() { |*[key, val].take(arity)| ... } click to toggle source

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.

@example

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 }

@overload hsh.select!{|key, val| block } -> hsh or nil

Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.
@return [Hashlike]

@overload hsh.select! -> an_enumerator

with no block, returns a raw enumerator
@return [Enumerator]
# 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) click to toggle source

alias for []=

# File lib/gorillib/hashlike.rb, line 214
def store(key, val)
  self[key] = val
end
to_hash() click to toggle source

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

@example

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

@return [Hash] a new Hash instance, with each key set to its associated value.

# File lib/gorillib/hashlike.rb, line 744
def to_hash
  {}.tap{|hsh| each_pair{|key, val| hsh[key] = val } }
end
update(other_hash) { |key, val, self| ... } click to toggle source

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.

@example

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 }

@overload hsh.update(other_hash) -> hsh

Adds the contents of +other_hash+ to +hsh+.  Entries with duplicate keys are
overwritten with the values from +other_hash+
@param  other_hash [Hash, Hashlike] the hash to merge (it wins)
@return [Hashlike] this hashlike, updated

@overload hsh.update(other_hash){|key, oldval, newval| block} -> hsh

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+.
@param  other_hash [Hash, Hashlike] the hash to merge (it wins)
@yield  [Object, Object, Object] called if key exists in each +hsh+
@return [Hashlike] this hashlike, updated
# 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) click to toggle source

Array containing the values associated with the given keys.

@see Hashlike#select.

@example

hsh = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
hsh.values_of("cow", "cat")  # => ["bovine", "feline"]

@example

hsh = { :a => 100, :b => 200, :c => 300 }
hsh.values_of(:c, :a, :c, :z, :a)
# => [300, 100, 300, nil, 100]

@param allowed_keys [Object] the keys to retrieve. @return [Array] the values, in order according to allowed_keys.

# 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