Class: Hash

Inherits:
Object show all
Defined in:
lib/nano/hash/inverse.rb,
lib/nano/hash/at.rb,
lib/nano/hash/each.rb,
lib/nano/hash/join.rb,
lib/nano/hash/to_h.rb,
lib/nano/hash/slice.rb,
lib/nano/hash/weave.rb,
lib/nano/hash/%3C%3C.rb,
lib/nano/hash/%5B%5D.rb,
lib/nano/hash/collate.rb,
lib/nano/hash/shuffle.rb,
lib/nano/hash/swap%21.rb,
lib/nano/hash/alias%21.rb,
lib/nano/hash/graph%21.rb,
lib/nano/hash/rand_key.rb,
lib/nano/hash/traverse.rb,
lib/nano/hash/%5B%5D%3D.rb,
lib/nano/hash/keys_to_s.rb,
lib/nano/hash/rand_pair.rb,
lib/nano/hash/collate%21.rb,
lib/nano/hash/rand_value.rb,
lib/nano/hash/shuffle%21.rb,
lib/nano/hash/swapkey%21.rb,
lib/nano/hash/to_ostruct.rb,
lib/nano/hash/has_keys%3F.rb,
lib/nano/hash/keys_to_sym.rb,
lib/nano/hash/traverse%21.rb,
lib/nano/hash/update_each.rb,
lib/nano/hash/%3A%3Azipnew.rb,
lib/nano/hash/keys_to_s%21.rb,
lib/nano/hash/replace_each.rb,
lib/nano/hash/delete_unless.rb,
lib/nano/hash/keys_to_sym%21.rb,
lib/nano/hash/assert_has_keys.rb,
lib/nano/hash/has_only_keys%3F.rb,
lib/nano/hash/to_ostruct_recurse.rb,
lib/nano/hash/assert_has_only_keys.rb

Overview

Daniel Schierbeck

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.zipnew(keys, values) ⇒ Object

Creates a new hash from two arrays –a keys array and a values array.

Hah.zipnew(["a","b","c"], [1,2,3])
  #=> { "a"=>1, "b"=>2, "c"=>3 }


9
10
11
12
13
# File 'lib/nano/hash/%3A%3Azipnew.rb', line 9

def self.zipnew(keys,values) # or some better name
  h = {}
  keys.size.times{ |i| h[ keys[i] ] = values[i] }
  h
end

Instance Method Details

#[](*sliceKeys) ⇒ Object

Adds slicing to Hash#[]. If more than one key arguments is given to Hash#[], the return value will be an array of the corresponding values.

h = { :a => 1, :b => 2, :c => 3 }
h[:a]            #=> 1
h[:a, :b]        #=> [1,2]


13
14
15
16
17
18
19
# File 'lib/nano/hash/%5B%5D.rb', line 13

def []( *sliceKeys )
  if sliceKeys.length == 1
    return at( sliceKeys[0] )
  else
    return values_at( *sliceKeys )
  end
end

#[]=(*args) ⇒ Object

Add slicing to element assignment operator.

h = {:a=>1, :b=>2, :c=>3}

h[:a] = 9              #=> 9
h                      #=> {:a=>9, :b=>2, :c=>3}

h[:a, :c] = [10,11]    #=> [10,11]
h                      #=> {:a=>10, :b=>2, :c=>11}


14
15
16
17
18
19
20
21
# File 'lib/nano/hash/%5B%5D%3D.rb', line 14

def []=( *args )
  if args.length <= 2
    return store( *args )
  end
  aVals = args.pop
  aVals = [aVals] unless aVals.kind_of?( Array )
  args.each_index{ |i| store( args[i], aVals[i] ) }
end

#alias!(newkey, oldkey) ⇒ Object

Modifies the receiving Hash so that the value previously referred to by oldkey is also referenced by newkey; oldkey is retained in the Hash. If oldkey does not exist as a key in the Hash, no change is effected.

Returns a reference to the Hash.

foo = { :name=>'Gavin', 'wife'=>:Lisa }
foo.alias!('name',:name)     => { :name=>'Gavin', 'name'=>'Gavin', 'wife'=>:Lisa }

foo = { :name=>'Gavin', 'wife'=>:Lisa }
foo.alias!('spouse','wife')  => { :name=>'Gavin', 'wife'=>:Lisa, 'spouse'=>:Lisa }

foo = { :name=>'Gavin', 'wife'=>:Lisa }
foo.alias!('bar','foo')      => { :name=>'Gavin', 'wife'=>:Lisa }

Note that if the oldkey is reassigned, the reference will no longer exist, and the newkey will remain as it was.



23
24
25
26
# File 'lib/nano/hash/alias%21.rb', line 23

def alias!( newkey, oldkey )
  self[newkey] = self[oldkey] if self.has_key?(oldkey)
  self
end

#assert_has_keys(*check_keys) ⇒ Object

Returns true is hash has the given keys, otherwise throws an ArgumentError.

h = { :a => 1, :b => 2 }
h.assert_has_keys( :a )   #=> true
h.assert_has_keys( :c )   #=> ArgumentError

Raises:

  • (ArgumentError)


10
11
12
# File 'lib/nano/hash/assert_has_keys.rb', line 10

def assert_has_keys(*check_keys)
  raise(ArgumentError, "does not have key(s)") unless has_keys?(*check_keys)
end

#assert_has_only_keys(*check_keys) ⇒ Object

Returns true is hash has only then given keys, otherwise throws an ArgumentError.

h = { :a => 1, :b => 2 }
h.assert_has_only_keys( :a, :b )   #=> true
h.assert_has_only_keys( :a )       #=> ArgumentError

Raises:

  • (ArgumentError)


10
11
12
# File 'lib/nano/hash/assert_has_only_keys.rb', line 10

def assert_has_only_keys(*check_keys)
  raise(ArgumentError, "has unexpected key(s)") unless has_only_keys?(*check_keys)
end

#atObject



2
# File 'lib/nano/hash/at.rb', line 2

alias_method( :at, :[] )

#collateObject

Returns a new hash built by iterating through each key,value pair and updating the new hash.

– Note that this method may get some fine tuning as it currently expects the block to return a “mini-hash” pair – parhaps a 2-element array woud be better? ++



13
14
15
16
17
# File 'lib/nano/hash/collate.rb', line 13

def collate  # :yield:
  newhash = {}
  each_pair{ |k,v| newhash.update( yield(k,v) ) }
  newhash
end

#collate!(&yld) ⇒ Object

In place version of #collate.



6
7
8
# File 'lib/nano/hash/collate%21.rb', line 6

def collate!(&yld)
  replace( collate(&yld) )
end

#delete_unless(&block) ⇒ Object



3
4
5
# File 'lib/nano/hash/delete_unless.rb', line 3

def delete_unless (&block)
  delete_if { |key, value| not block.call(key, value) }
end

#each(&yld) ⇒ Object

A “smarter” hash#each which iterates through each value if only one block parameter is given.

{:a=>"a", 2=>"b", "x"=>"c"}.each{ |v| puts v }

produces

a
b
c

WARNING! Use with caution. Methods from other libraries may depend on the old behavior, expecting a two element array to be passed into a single block argument.



19
20
21
22
23
24
25
26
27
28
# File 'lib/nano/hash/each.rb', line 19

def each(&yld)
  case yld.arity
  when 0
  when 1
    each_value{|v| yield(v)}
  else
    each_pair{|k,v| yld.call(k,v)}
  end
  self
end

#graph!(&blk) ⇒ Object

As with Enumerable#graph but acts in place.



5
6
7
# File 'lib/nano/hash/graph%21.rb', line 5

def graph!( &blk )
  self.replace( graph( &blk ) )
end

#has_keys?(*check_keys) ⇒ Boolean

Returns true or false whether the hash contains the given keys.

h = { :a => 1, :b => 2 }
h.has_keys?( :a )   #=> true
h.has_keys?( :c )   #=> false

Returns:

  • (Boolean)


10
11
12
13
# File 'lib/nano/hash/has_keys%3F.rb', line 10

def has_keys?(*check_keys)
  unknown_keys = check_keys - self.keys
  return unknown_keys.empty?
end

#has_only_keys?(*check_keys) ⇒ Boolean

Returns true if the hash contains only the given keys, otherwise false.

h = { :a => 1, :b => 2 }
h.has_only_keys?( :a, :b )   #=> true
h.has_only_keys?( :a )       #=> false

Returns:

  • (Boolean)


10
11
12
13
# File 'lib/nano/hash/has_only_keys%3F.rb', line 10

def has_only_keys?(*check_keys)
  unknown_keys = self.keys - check_keys
  return unknown_keys.empty?
end

#inverseObject

Create a “true” inverse hash by storing mutliple values in Arrays.

h = {"a"=>3, "b"=>3, "c"=>3, "d"=>2, "e"=>9, "f"=>3, "g"=>9}

h.invert                #=> {2=>"d", 3=>"f", 9=>"g"}
h.inverse               #=> {2=>"d", 3=>["f", "c", "b", "a"], 9=>["g", "e"]}
h.inverse.inverse       #=> {"a"=>3, "b"=>3, "c"=>3, "d"=>2, "e"=>9, "f"=>3, "g"=>9}
h.inverse.inverse == h  #=> true


16
17
18
19
20
21
22
23
24
25
26
# File 'lib/nano/hash/inverse.rb', line 16

def inverse
  i = Hash.new
  self.each_pair{ |k,v|
    if (Array === v)
      v.each{ |x| i[x] = ( i.has_key?(x) ? [k,i[x]].flatten : k ) }
    else
      i[v] = ( i.has_key?(v) ? [k,i[v]].flatten : k )
    end
  }
  return i
end

#join(pair_divider = '', elem_divider = '') ⇒ Object



2
3
4
5
6
# File 'lib/nano/hash/join.rb', line 2

def join( pair_divider='', elem_divider='' )
  s = []
  each_pair { |k,v| s << "#{k}#{pair_divider}#{v}" }
  s.join(elem_divider)
end

#keys_to_s(from_class = nil) ⇒ Object Also known as: stringify_keys

Converts all keys in the Hash to be String values, returning a new Hash. With a from_class parameter, limits conversion to only a certain class of keys. It defaults to nil which convert any key class.

foo = { :name=>'Gavin', :wife=>:Lisa }
foo.keys_to_s    #=>  { "name"=>"Gavin", "wife"=>:Lisa }
foo.inspect      #=>  { :name =>"Gavin", :wife=>:Lisa }

– Adapted from Gavin Sinclair’s #convert_keys. ++

Raises:

  • (ArgumentError)


16
17
18
19
# File 'lib/nano/hash/keys_to_s.rb', line 16

def keys_to_s( from_class=nil )
  raise ArgumentError, "Parameter must be a class" unless from_class.kind_of?(Class) if from_class
  self.dup.keys_to_s!( from_class )
end

#keys_to_s!(from_class = nil) ⇒ Object Also known as: stringify_keys!

Synonym for Hash#keys_to_string, but modifies the receiver in place (and returns it). With a from_class parameter, limits conversion to only a certain class of keys. It defaults to nil which convert any key class.

foo = { :name=>'Gavin', :wife=>:Lisa }
foo.keys_to_s!    #=>  { "name"=>"Gavin", "wife"=>:Lisa }
foo.inspect       #=>  { "name"=>"Gavin", "wife"=>:Lisa }

– Adapted from Gavin Sinclair’s #convert_keys. ++

Raises:

  • (ArgumentError)


16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/nano/hash/keys_to_s%21.rb', line 16

def keys_to_s!( from_class=nil )
  raise ArgumentError, "Parameter must be a class" unless from_class.kind_of?(Class) if from_class
  if from_class
    self.each_key{ |k|
      self[k.to_s]=self.delete(k) if k.respond_to?(:to_s) and k.class == from_class
    }
  else
    self.each_key{ |k|
      self[k.to_s]=self.delete(k) if k.respond_to?(:to_s)
    }
  end
  self
end

#keys_to_sym(from_class = String) ⇒ Object Also known as: symbolize_keys

Converts all keys in the Hash to be Symbol values, returning a new Hash. With a from_class parameter, limits conversion to only a certain class of keys. It defaults to String –use nil to convert any key class.

foo = { :name=>'Gavin', 'wife'=>:Lisa }
foo.keys_to_sym    #=>  { :name=>"Gavin", :wife=>:Lisa }
foo.inspect        #=>  { "name" =>"Gavin", "wife"=>:Lisa }

– Adapted from Gavin Sinclair’s graceous work #symbolify_keys. ++

Raises:

  • (ArgumentError)


16
17
18
19
# File 'lib/nano/hash/keys_to_sym.rb', line 16

def keys_to_sym( from_class=String )
  raise ArgumentError, "Parameter must be a class" unless from_class.kind_of?(Class) if from_class
  self.dup.keys_to_sym!( from_class )
end

#keys_to_sym!(from_class = String) ⇒ Object Also known as: symbolize_keys!

Synonym for Hash#keys_to_symbol, but modifies the receiver in place (and returns it). With a from_class parameter, limits conversion to only a certain class of keys. It defaults to String –use nil to convert any key class.

foo = { 'name'=>'Gavin', 'wife'=>:Lisa }
foo.keys_to_sym!    #=>  { :name=>"Gavin", :wife=>:Lisa }
foo.inspect         #=>  { :name=>"Gavin", :wife=>:Lisa }

– Adapted from Gavin Sinclair’s graceous work #symbolify_keys! ++

Raises:

  • (ArgumentError)


16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/nano/hash/keys_to_sym%21.rb', line 16

def keys_to_sym!( from_class=String )
  raise ArgumentError, "Parameter must be a class" unless from_class.kind_of?(Class) if from_class
  if from_class
    self.each_key{ |k|
      self[k.to_sym]=self.delete(k) if k.respond_to?(:to_sym) and k.class == from_class
    }
  else
    self.each_key{ |k|
      self[k.to_sym]=self.delete(k) if k.respond_to?(:to_sym)
    }
  end
  self
end

#rand_keyObject

Returns a random key.

{:one => 1, :two => 2, :three => 3}.pick_key  #=> :three


7
8
9
# File 'lib/nano/hash/rand_key.rb', line 7

def rand_key
  keys.at( rand(keys.size) )
end

#rand_key!Object Also known as: pick_key

Delete a random key-value pair, returning the key.

a = {:one => 1, :two => 2, :three => 3}
a.pick_key!  #=> :two
a            #=> {:one => 1, :three => 3}


17
18
19
20
21
# File 'lib/nano/hash/rand_key.rb', line 17

def rand_key!
  k,v = rand_pair
  delete( k )
  return k
end

#rand_pairObject

Returns a random key-value pair.

{:one => 1, :two => 2, :three => 3}.pick  #=> [:one, 1]


7
8
9
10
# File 'lib/nano/hash/rand_pair.rb', line 7

def rand_pair
  k = rand_key
  return k, fetch(k)
end

#rand_pair!Object Also known as: pick_pair

Deletes a random key-value pair and returns that pair.

a = {:one => 1, :two => 2, :three => 3}
a.rand_pair!  #=> [:two, 2]
a             #=> {:one => 1, :three => 3}


18
19
20
21
22
# File 'lib/nano/hash/rand_pair.rb', line 18

def rand_pair!
  k,v = rand_pair
  delete( k )
  return k,v
end

#rand_valueObject Also known as: at_rand

Returns a random hash value.

{:one => 1, :two => 2, :three => 3}.rand_value  #=> 2
{:one => 1, :two => 2, :three => 3}.rand_value  #=> 1


14
15
16
# File 'lib/nano/hash/rand_value.rb', line 14

def rand_value
  fetch( rand_key )
end

#rand_value!Object Also known as: pick, at_rand!

Deletes a random key-value pair and returns the value.

a = {:one => 1, :two => 2, :three => 3}
a.at_rand!  #=> 2
a           #=> {:one => 1, :three => 3}


24
25
26
27
28
# File 'lib/nano/hash/rand_value.rb', line 24

def rand_value!
  k,v = rand_pair
  delete( k )
  return v
end

#replace_eachObject

Same as #update_each, but deletes the key element first.



4
5
6
7
# File 'lib/nano/hash/replace_each.rb', line 4

def replace_each  # :yield:
  dup.each_pair{ |k,v| delete( k ); update( yield(k,v) ); }
  self
end

#shuffleObject

Returns a copy of the hash with values arranged in new random order.

h = {:a=>1, :b=>2, :c=>3}
h.shuffle_hash  #=> {:b=>2, :c=>1, :a>3}


11
12
13
# File 'lib/nano/hash/shuffle.rb', line 11

def shuffle
  Hash.zipnew( keys.sort_by{Kernel.rand}, values.sort_by{Kernel.rand} )
end

#shuffle!Object

Destructive shuffle_hash. Arrange the values in a new random order.

h = {:a => 1, :b => 2, :c => 3}
h.shuffle_hash!
h  #=> {:b=>2, :c=>1, :a=>3}


12
13
14
# File 'lib/nano/hash/shuffle%21.rb', line 12

def shuffle!
  self.replace( shuffle )
end

#swap!(key1, key2) ⇒ Object

Swap the values of a pair of keys in place.

{:a=>1,:b=>2}.swap!  #=> {:a=>2,:b=>1}


7
8
9
10
11
12
# File 'lib/nano/hash/swap%21.rb', line 7

def swap!( key1, key2 )
  tmp = self[key1]
  self[key1] = self[key2]
  self[key2] = tmp
  self
end

#swapkey!(newkey, oldkey) ⇒ Object

Modifies the receiving Hash so that the value previously referred to by oldkey is referenced by newkey; oldkey is removed from the Hash. If oldkey does not exist as a key in the Hash, no change is effected.

Returns a reference to the Hash.

foo = { :a=>1, :b=>2 }
foo.swapkey!('a',:a)       #=> { 'a'=>1, :b=>2 }
foo.swapkey!('b',:b)       #=> { 'a'=>1, 'b'=>2 }
foo.swapkey!('foo','bar')  #=> { 'a'=>1, 'b'=>2 }

– Credit goes to Gavin Sinclair. ++



16
17
18
19
# File 'lib/nano/hash/swapkey%21.rb', line 16

def swapkey!( newkey, oldkey )
  self[newkey] = self.delete(oldkey) if self.has_key?(oldkey)
  self
end

#to_hObject

Return a rehashing of self.

{"a"=>1,"b"=>2}.to_h  #=> {"b"=>2,"a"=>1}

– A horse is a horse, a horse of course. ++



10
# File 'lib/nano/hash/to_h.rb', line 10

def to_h; rehash; end

#to_ostructObject

Turns a hash into a generic object using an OpenStruct.

o = { 'a' => 1 }.to_ostruct
o.a  #=> 1


10
11
12
# File 'lib/nano/hash/to_ostruct.rb', line 10

def to_ostruct
  OpenStruct.new( self )
end

#to_ostruct_recurseObject

Like to_ostruct but recusively objectifies all hash elements as well.

o = { 'a' => { 'b' => 1 } }.to_ostruct_recurse
o.a.b  #=> 1


11
12
13
14
15
16
17
# File 'lib/nano/hash/to_ostruct_recurse.rb', line 11

def to_ostruct_recurse
  o = self.dup
  each_pair do |k,v|
    o[k] = v.to_ostruct_recurse if v.respond_to?( :to_ostruct_recurse )
  end
  OpenStruct.new( o )
end

#traverse(&b) ⇒ Object

Returns a new hash created by traversing the hash and its subhashes, executing the given block on the key and value. The block should return a 2-element array of the form [key, value].

h = { "A"=>"A", "B"=>"B" }
h.traverse { |k,v| [k.downcase, v] }
h  #=> { "a"=>"A", "b"=>"B" }


13
14
15
16
17
18
19
# File 'lib/nano/hash/traverse.rb', line 13

def traverse(&b)
  inject({}) do |h,(k,v)|
    nk, nv = b[k,v]
    h[nk] = ( Hash === v ? v.traverse(&b) : nv )
    h
  end
end

#traverse!(&yld) ⇒ Object

In place version of traverse, which traverses the hash and its subhashes, executing the given block on the key and value.

h = { "A"=>"A", "B"=>"B" }
h.traverse! { |k,v| [k.downcase, v] }
h  #=> { "a"=>"A", "b"=>"B" }


12
13
14
# File 'lib/nano/hash/traverse%21.rb', line 12

def traverse!( &yld )
  self.replace( self.traverse( &yld ) )
end

#update_eachObject

Iterates through each pair and updates a the hash in place. This is formally equivalent to #collate! But does not use #collate to accomplish the task. Hence #update_each is probably a bit faster.

# TODO

– Note that this may get some fine tuning as currently it expects the block to return a “mini-hash” pair. ++



14
15
16
17
# File 'lib/nano/hash/update_each.rb', line 14

def update_each  # :yield:
  dup.each_pair{ |k,v| update( yield(k,v) ); }
  self
end

#weave(h) ⇒ Object

Weaves two hashes producing a new hash. The two hashes need to be compatible according to the following rules for each node:

<tt>
hash, hash => hash (recursive +)
hash, array => error
hash, value => error
array, hash => error
array, array => array + array
array, value => array << value
value, hash => error
value, array => array.unshift(valueB)
valueA, valueB => valueB
</tt>

Example:

# to do

Raises:

  • (ArgumentError)


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/nano/hash/weave.rb', line 23

def weave(h)
  raise ArgumentError, "Hash expected" unless h.kind_of?(Hash)
  s = self.clone
  h.each { |k,node|
    node_is_hash = node.kind_of?(Hash)
    node_is_array = node.kind_of?(Array)
    if s.has_key?(k)
      self_node_is_hash = s[k].kind_of?(Hash)
      self_node_is_array = s[k].kind_of?(Array)
      if self_node_is_hash
        if node_is_hash
          s[k] = s[k].weave(node)
        elsif node_is_array
          raise ArgumentError, 'Incompatible hash addition' #self[k] = node
        else
          raise ArgumentError, 'Incompatible hash addition' #self[k] = node
        end
      elsif self_node_is_array
        if node_is_hash
          raise ArgumentError, 'Incompatible hash addition' #self[k] = node
        elsif node_is_array
          s[k] += node
        else
          s[k] << node
        end
      else
        if node_is_hash
          raise ArgumentError, 'Incompatible hash addition' #self[k] = node
        elsif node_is_array
          s[k].unshift( node )
        else
          s[k] = node
        end
      end
    else
      s[k] = node
    end
  }
  s
end