Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Ruby Arrays & Hashes
   with Examples
Arrays & Hashes

 Like all high-level languages, Ruby has built-in support for arrays, objects that
  contain ordered lists of other objects.

 Each element is a reference to some object
 The object references can be -
      predefined variables
      anonymous objects created on the spot ('my string', 4.7, or
       MyClass.new)
      expressions (a+b, object.method).
 You can use arrays (often in conjunction with hashes) to build and use
  complex data structures without having to define any custom classes.

 Hash is a collection data structure consisting of key-value pairs. Key and value can be any
    object.
a1 = []                             # => []
a2 = [1, 2, 3]                     # => [1, 2, 3]
a3 = [1, 2, 3, 'a', 'b', 'c', nil] # => [1, 2, 3, "a", "b", "c", nil]
n1 = 4
n2 = 6
sum_and_difference = [n1, n2, n1+n2, n1-n2]              # => [4, 6, 10, -2]

%w{1 2 3}                            # => ["1", "2", "3"]
%w{The rat sat on the mat}           # => ["The", "rat", "sat", "on", "the", "mat"]

a = [1, 2, 3]       # => [1, 2, 3]
a << 4.0            # => [1, 2, 3, 4.0]
a << 'five‘         # => [1, 2, 3, 4.0, "five"]

a = [1,2,3]         # => [1, 2, 3]
a << [4, 5, 6]      # => [1, 2, 3, [4, 5, 6]]
a << a              # => [1, 2, 3, [4, 5, 6], […]]
a = [1, 2, 3, [4, 5, 6]]
a.size                     # => 4
a << a                     # => [1, 2, 3, [4, 5, 6], […]]
a.size                     # => 5

a[0]                       # => 1
a[3]                       # => [4, 5, 6]
a[3][0]                    # => 4
a[3].size                  # => 3

a[-2]                      # => [4, 5, 6]
a[-1]                      # => [1, 2, 3, [4, 5, 6], […]]
a[a.size-1]                # => [1, 2, 3, [4, 5, 6], […]]

a[-1][-1]                  # => [1, 2, 3, [4, 5, 6], […]]
a[-1][-1][-1]              # => [1, 2, 3, [4, 5, 6], […]]
Iterating over an array
 Iterate over the array with Enumerable#each. Put into a block the code you
 want to execute for each item in the array.

    [1, 2, 3, 4].each { |x| puts x }

 If you want to produce a new array based on a transformation of some other array, use
  Enumerable#collect along with a block that takes one element and transforms it

            [1, 2, 3, 4].collect { |x| x ** 2 } # => [1, 4, 9, 16]

 If you need to have the array indexes along with the array elements, use
  Enumerable#each_with_index.

               ['a', 'b', 'c'].each_with_index do |item, index|
                            puts "At position #{index}: #{item}“
               end

 To iterate over a list in reverse order, use the reverse_each method

            [1, 2, 3, 4]. reverse_each { |x| puts x }
Continued …
 Enumerable#collect has a destructive equivalent: Array# collect!, also known as Array#map!
 It replaces each item in the old array with the corresponding value from the code block.
 This saves memory and time, but it destroys the old array

array = ['a', 'b', 'c']
array.collect! { |x| x.upcase }
array                         # => ["A", "B", "C"]

array.map! { |x| x.downcase }
array # => ["a", "b", "c"]

 If you need to skip certain elements of an array, you can use the iterator methods
  Range#step and Integer#upto instead of Array#each


     3.upto(array.length-1) { |i| puts "Value #{array[i]}" }

  (0..array.length-1).step(2) do |i|
              puts "Letter #{array[i]} is #{array[i+1]}"
  end
Continued…

 To add an array of key-value pairs to a hash, either iterate over the array with Array#each, or
  pass the hash into Array#inject.
 Using inject is slower but the code is more concise.



squares = [[1,1], [2,4], [3,9]]

results = {}
squares.each { |k,v| results[k] = v }
results      # => {1=>1, 2=>4, 3=>9}

squares.inject({}) { |h, kv| h[kv[0]] = kv[1]; h }
# => {1=>1, 2=>4, 3=>9}
Continued…

 To turn a flat array into the key-value pairs of a hash, iterate over the array elements two at a
  time

class Array
 def into_hash(h)
      unless size % 2 == 0
        raise StandardError, "Expected array with even number of elements“
      end
      0.step(size-1, 2) { |x| h[self[x]] = self[x+1] }
      h
  end
 end

squares = [1,1,2,3,4,9]
results = {}
squares.into_hash(results) # => {1=>1, 2=>3, 4=>9}

[1,1,2].into_hash(results) # StandardError: Expected array with even number of elements
Continued…

 To insert into a hash every key-value from another hash, use Hash#merge!.
 If a key is present in both hashes when a.merge!(b) is called, the value in b takes precedence
  over the value in a.

squares = { 1 => 1, 2 => 4, 3 => 9}
cubes = { 3 => 27, 4 => 256, 5 => 3125}

squares.merge!(cubes)
squares          # =>{5=>3125, 1=>1, 2=>4, 3=>27, 4=>256}
cubes           # =>{5=>3125, 3=>27, 4=>256}


 Hash#merge! also has a nondestructive version, Hash#merge, which creates a new Hash with
  elements from both parent hashes.
 Again, the hash passed in as an argument takes precedence.
 To completely replace the entire contents of one hash with the contents of another, use
  Hash#replace.

squares = { 1 => 1, 2 => 4, 3 => 9}
cubes = { 1 => 1, 2 => 8, 3 => 27}
squares.replace(cubes)
squares      # => {1=>1, 2=>8, 3=>27}

 This is different from simply assigning the cubes hash to the squares variable name, because
  cubes and squares are still separate hashes.
 They just happen to contain the same elements in the above example.
 Changing cubes won't affect squares.

cubes[4] = 64
squares     # => {1=>1, 2=>8, 3=>27}

 Hash#replace is useful for reverting a Hash to known default values.
Removing elements from a Hash
 To remove specific element from hash, pass the key into Hash#delete
h = {}
h[1] = 10
h             # => {1=>10}
h.delete(1)
h             # => {}

 Don't try to delete an element from a hash by mapping it to nil.
 It's true that, by default, you get nil when you look up a key that's not in the hash, but there's
  a difference between a key that's missing from the hash and a key that's present but mapped
  to nil.
 Hash#has_key? will see a key mapped to nil, as will Hash#each and all other methods except
  for a simple fetch.

h = {}
h[5]           # => nil
h[5] = 10
h[5]           # => 10
h[5] = nil
h[5]           # => nil
h.keys         # => [5]
h.delete(5)
h.keys         # => []
Example
 Use the Hash#delete_if iterator to delete key-value pairs for which a certain code block returns true
 Hash#reject works the same way, but it works on a copy of the Hash
class Hash
 def delete_value(value)
  delete_if { |k,v| v == value }
 end
end
h = {'apple' => 'green', 'potato' => 'red', 'sun' => 'yellow', 'katydid' => 'green' }
h.delete_value('green')
h                   # => {"sun"=>"yellow", "potato"=>"red"}

   The below code implements the opposite of Hash#merge; it extracts one hash from another
class Hash
 def remove_hash(other_hash)
  delete_if { |k,v| other_hash[k] == v }
 end
end
squares = { 1 => 1, 2 => 4, 3 => 9 }
doubles = { 1 => 2, 2 => 4, 3 => 6 }
squares.remove_hash(doubles)
squares              # => {1=>1, 3=>9}

   To wipe out the entire contents of a Hash, use Hash#clear
Using an array or other modifiable object as hash key

 A naive solution tends to lose hash values once the keys are modified


coordinates = [10, 5]
treasure_map = { coordinates => 'jewels' }
treasure_map[coordinates]            # => "jewels“


# Add a z-coordinate to indicate how deep the treasure is buried.
coordinates << -5
coordinates                          # => [10, 5, -5]
treasure_map[coordinates]            # => nil


 The easiest solution is to call the Hash#rehash method every time you modify one of the

    hash's keys
treasure_map.rehash
treasure_map[coordinates]            # => "jewels"
Continued…

module ReliablyHashable
 def hash
  return object_id
 end
end

class ReliablyHashableArray < Array
 include ReliablyHashable
end

coordinates = ReliablyHashableArray.new([10,5])
treasure_map = { coordinates => 'jewels' }
treasure_map[coordinates]       # => "jewels“

# Add a z-coordinate to indicate how deep the treasure is buried.
coordinates.push(-5)
treasure_map[coordinates]        # => "jewels"
Continued…
 The implementation of hash given in the previous solution violates the principle that
  different representations of the same data should have the same hash code.
 This means that two ReliablyHashableArray objects will have different hash codes even if
  they have the same contents

a = [1,2]
b = a.clone
a.hash                  # => 11
b.hash                  # => 11



a = ReliablyHashableArray.new([1,2])
b = a.clone
a.hash                   # => -606031406
b.hash                   # => -606034266


 The solution is to freeze your hash keys.
 Any frozen object can be reliably used as a hash key, since you can't do anything to a frozen
  object that would cause its hash code to change
Keeping multiple values for the same hash key
hash = Hash.new { |hash, key| hash[key] = [] }
raw_data = [ [1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, ['b', 'c']], [3, 'c'] ]
raw_data.each { |x,y| hash[x] << y }
hash      # => {1=>["a", "b", "c"], 2=>["a", ["b", "c"]], 3=>["c"]}


 It's possible to subclass Hash to act like a normal hash until a key collision occurs, and then
  start keeping an array of values for the key that suffered the collision

class MultiValuedHash < Hash
 def []=(key, value)
  if has_key?(key)
       super(key, [value, self[key]].flatten)
  else
    super
  end
  end
end

hash = MultiValuedHash.new
raw_data.each { |x,y| hash[x] = y }
hash          # => {1=>["c", "b", "a"], 2=>["b", "c", "a"], 3=>"c"}
Iterating over a hash
 Note that each and each_pair return the key-value pairs in an apparently random order
hash = { 1 => 'one', [1,2] => 'two', 'three' => 'three' }
hash.each_pair { |key, value| puts "#{key.inspect} maps to #{value}"}
# [1, 2] maps to two
# "three" maps to three
# 1 maps to one


 Use Hash#each_key if you only need the keys of a hash
active_toggles = { 'super' => true, 'meta' => true, 'hyper' => true }
active_toggles.each_key { |active| puts active }

# hyper # meta # super



 Use Hash#each_value if you only need the values of a hash
favorite_colors = { 'Alice' => :red, 'Bob' => :violet, 'Mallory' => :blue, 'Carol' => :blue, 'Dave' => :violet }
summary = Hash.new(0)
favorite_colors.each_value { |x| summary[x] += 1 }
summary # => {:red=>1, :violet=>2, :blue=>2}
Continued…
 Don't modify the keyset of a hash during an iteration, or you'll get undefined results and
  possibly a RuntimeError
1.upto(100) { |x| hash[x] = true }
hash.keys { |k| hash[k * 2] = true }
# RuntimeError: hash modified during iteration


 Using an array as intermediary
hash = {1 => 2, 2 => 2, 3 => 10}
hash.keys                      # => [1, 2, 3]
hash.values                    # => [2, 2, 10]
hash.to_a                      # => [[1, 2], [2, 2], [3, 10]]


 Sorting the result of Hash#keys.
extensions = { 'Alice' => '104', 'Carol' => '210', 'Bob' => '110' }
extensions.keys.sort.each do |k|
  puts "#{k} can be reached at extension ##{extensions[k]}“
end
# Alice can be reached at extension #104
# Bob can be reached at extension #110
# Carol can be reached at extension #210
Continued…
 Hash#sort and Hash#sort_by turn a hash into an array of two-element subarrays (one for
  each key-value pair), then sort the array of arrays however you like
 This code sorts a to-do list by priority, then alphabetically
to_do = { 'Clean car' => 5, 'Take kangaroo to vet' => 3, 'Realign plasma conduit' => 3 }
to_do.sort_by { |task, priority| [priority, task] }.each { |k,v| puts k }

# Realign plasma conduit
# Take kangaroo to vet
# Clean car

 This code sorts a hash full of number pairs according to the magnitude of the difference
  between the key and the value
transform_results = { 4 => 8, 9 => 9, 10 => 6, 2 => 7, 6 => 5 }
by_size_of_difference = transform_results.sort_by { |x, y| (x-y).abs }
by_size_of_difference.each { |x, y| puts "f(#{x})=#{y}: difference #{y-x}" }

# f(9)=9: difference 0
# f(6)=5: difference -1
# f(10)=6: difference -4
# f(4)=8: difference 4
# f(2)=7: difference 5
Iterating over a hash in insertion order
 To iterate over a hash in the order in which the elements were added to the hash.
 Use the orderedhash library
require 'orderedhash‘
h = OrderedHash.new
h[1] = 1
h["second"] = 2
h[:third] = 3
h.keys                         # => [1, "second", :third]
h.values                       # => [1, 2, 3]
h.each { |k,v| puts "The #{k} counting number is #{v}" }
# The 1 counting number is 1 # The second counting number is 2   # The third counting number is 3

 OrderedHash is a subclass of Hash that keeps an array of the keys in insertion order.
 When you add key-value pair to the hash, OrderedHash modifies both the underlying hash
  and the array
 But any operation that modifies an OrderedHash may also modify the internal array, so it's
  slower than just using a hash.
 OrderedHash#delete is especially slow, since it must perform a linear search of the internal
  array to find the key being deleted.
 Hash#delete runs in constant time, but OrderedHash#delete takes time proportionate to size
  of the hash.
Inverting a hash

    To switch the keys and values of hash

phone_directory = { 'Alice' => '555-1212', 'Bob' => '555-1313', 'Mallory' => '111-1111' }
phone_directory.invert
# => {"111-1111"=>"Mallory", "555-1212"=>"Alice", "555-1313"=>"Bob"}



    Hash#invert probably won't do what you want if your hash maps more than one key to the same
     value.
    Only one of the keys for that value will show up as a value in the inverted hash

phone_directory = { 'Alice' => '555-1212', 'Bob' => '555-1313', 'Carol' => '555-1313', 'Mallory' => '111-1111', 'Ted' =>
     '555-1212' }
phone_directory.invert
# => {"111-1111"=>"Mallory", "555-1212"=>"Ted", "555-1313"=>"Bob"}



    To preserve all the data from the original hash, write a version of invert that keeps an array of
     values for each key …
Safe invert
class Hash
 def safe_invert
   new_hash = {}
   self.each do |k,v|
     if v.is_a? Array
       v.each { |x| new_hash.add_or_append(x, k) }
     else
       new_hash.add_or_append(v, k)
     end
   end
   return new_hash
 end
 def add_or_append(key, value)
   if has_key?(key)
          self[key] = [value, self[key]].flatten
   else
     self[key] = value
   end
  end
end

phone_directory.safe_invert               # => {"111-1111"=>"Mallory", "555-1212"=>["Ted", "Alice"], "555-1313"=>["Bob", "Carol"]}
phone_directory.safe_invert.safe_invert    # => {"Alice"=>"555-1212", "Mallory"=>"111-1111", "Ted"=>"555-1212", "Carol"=>"555-1313",
     "Bob"=>"555-1313"}
Continued…

 Ideally, if you called an inversion method twice you'd always get the same data you started
  with.

 The safe_invert method does better than invert on this score, but it's not perfect.

 If your original hash used arrays as hash keys, safe_invert will act as if you'd individually
  mapped each element in the array to the same value.

 Call safe_invert twice, and the arrays will be gone.
Thank you !

More Related Content

Ruby's Arrays and Hashes with examples

  • 1. Ruby Arrays & Hashes with Examples
  • 2. Arrays & Hashes  Like all high-level languages, Ruby has built-in support for arrays, objects that contain ordered lists of other objects.  Each element is a reference to some object  The object references can be -  predefined variables  anonymous objects created on the spot ('my string', 4.7, or MyClass.new)  expressions (a+b, object.method).  You can use arrays (often in conjunction with hashes) to build and use complex data structures without having to define any custom classes.  Hash is a collection data structure consisting of key-value pairs. Key and value can be any object.
  • 3. a1 = [] # => [] a2 = [1, 2, 3] # => [1, 2, 3] a3 = [1, 2, 3, 'a', 'b', 'c', nil] # => [1, 2, 3, "a", "b", "c", nil] n1 = 4 n2 = 6 sum_and_difference = [n1, n2, n1+n2, n1-n2] # => [4, 6, 10, -2] %w{1 2 3} # => ["1", "2", "3"] %w{The rat sat on the mat} # => ["The", "rat", "sat", "on", "the", "mat"] a = [1, 2, 3] # => [1, 2, 3] a << 4.0 # => [1, 2, 3, 4.0] a << 'five‘ # => [1, 2, 3, 4.0, "five"] a = [1,2,3] # => [1, 2, 3] a << [4, 5, 6] # => [1, 2, 3, [4, 5, 6]] a << a # => [1, 2, 3, [4, 5, 6], […]]
  • 4. a = [1, 2, 3, [4, 5, 6]] a.size # => 4 a << a # => [1, 2, 3, [4, 5, 6], […]] a.size # => 5 a[0] # => 1 a[3] # => [4, 5, 6] a[3][0] # => 4 a[3].size # => 3 a[-2] # => [4, 5, 6] a[-1] # => [1, 2, 3, [4, 5, 6], […]] a[a.size-1] # => [1, 2, 3, [4, 5, 6], […]] a[-1][-1] # => [1, 2, 3, [4, 5, 6], […]] a[-1][-1][-1] # => [1, 2, 3, [4, 5, 6], […]]
  • 5. Iterating over an array  Iterate over the array with Enumerable#each. Put into a block the code you want to execute for each item in the array. [1, 2, 3, 4].each { |x| puts x }  If you want to produce a new array based on a transformation of some other array, use Enumerable#collect along with a block that takes one element and transforms it [1, 2, 3, 4].collect { |x| x ** 2 } # => [1, 4, 9, 16]  If you need to have the array indexes along with the array elements, use Enumerable#each_with_index. ['a', 'b', 'c'].each_with_index do |item, index| puts "At position #{index}: #{item}“ end  To iterate over a list in reverse order, use the reverse_each method [1, 2, 3, 4]. reverse_each { |x| puts x }
  • 6. Continued …  Enumerable#collect has a destructive equivalent: Array# collect!, also known as Array#map!  It replaces each item in the old array with the corresponding value from the code block.  This saves memory and time, but it destroys the old array array = ['a', 'b', 'c'] array.collect! { |x| x.upcase } array # => ["A", "B", "C"] array.map! { |x| x.downcase } array # => ["a", "b", "c"]  If you need to skip certain elements of an array, you can use the iterator methods Range#step and Integer#upto instead of Array#each 3.upto(array.length-1) { |i| puts "Value #{array[i]}" } (0..array.length-1).step(2) do |i| puts "Letter #{array[i]} is #{array[i+1]}" end
  • 7. Continued…  To add an array of key-value pairs to a hash, either iterate over the array with Array#each, or pass the hash into Array#inject.  Using inject is slower but the code is more concise. squares = [[1,1], [2,4], [3,9]] results = {} squares.each { |k,v| results[k] = v } results # => {1=>1, 2=>4, 3=>9} squares.inject({}) { |h, kv| h[kv[0]] = kv[1]; h } # => {1=>1, 2=>4, 3=>9}
  • 8. Continued…  To turn a flat array into the key-value pairs of a hash, iterate over the array elements two at a time class Array def into_hash(h) unless size % 2 == 0 raise StandardError, "Expected array with even number of elements“ end 0.step(size-1, 2) { |x| h[self[x]] = self[x+1] } h end end squares = [1,1,2,3,4,9] results = {} squares.into_hash(results) # => {1=>1, 2=>3, 4=>9} [1,1,2].into_hash(results) # StandardError: Expected array with even number of elements
  • 9. Continued…  To insert into a hash every key-value from another hash, use Hash#merge!.  If a key is present in both hashes when a.merge!(b) is called, the value in b takes precedence over the value in a. squares = { 1 => 1, 2 => 4, 3 => 9} cubes = { 3 => 27, 4 => 256, 5 => 3125} squares.merge!(cubes) squares # =>{5=>3125, 1=>1, 2=>4, 3=>27, 4=>256} cubes # =>{5=>3125, 3=>27, 4=>256}  Hash#merge! also has a nondestructive version, Hash#merge, which creates a new Hash with elements from both parent hashes.  Again, the hash passed in as an argument takes precedence.
  • 10.  To completely replace the entire contents of one hash with the contents of another, use Hash#replace. squares = { 1 => 1, 2 => 4, 3 => 9} cubes = { 1 => 1, 2 => 8, 3 => 27} squares.replace(cubes) squares # => {1=>1, 2=>8, 3=>27}  This is different from simply assigning the cubes hash to the squares variable name, because cubes and squares are still separate hashes.  They just happen to contain the same elements in the above example.  Changing cubes won't affect squares. cubes[4] = 64 squares # => {1=>1, 2=>8, 3=>27}  Hash#replace is useful for reverting a Hash to known default values.
  • 11. Removing elements from a Hash  To remove specific element from hash, pass the key into Hash#delete h = {} h[1] = 10 h # => {1=>10} h.delete(1) h # => {}  Don't try to delete an element from a hash by mapping it to nil.  It's true that, by default, you get nil when you look up a key that's not in the hash, but there's a difference between a key that's missing from the hash and a key that's present but mapped to nil.  Hash#has_key? will see a key mapped to nil, as will Hash#each and all other methods except for a simple fetch. h = {} h[5] # => nil h[5] = 10 h[5] # => 10 h[5] = nil h[5] # => nil h.keys # => [5] h.delete(5) h.keys # => []
  • 12. Example  Use the Hash#delete_if iterator to delete key-value pairs for which a certain code block returns true  Hash#reject works the same way, but it works on a copy of the Hash class Hash def delete_value(value) delete_if { |k,v| v == value } end end h = {'apple' => 'green', 'potato' => 'red', 'sun' => 'yellow', 'katydid' => 'green' } h.delete_value('green') h # => {"sun"=>"yellow", "potato"=>"red"}  The below code implements the opposite of Hash#merge; it extracts one hash from another class Hash def remove_hash(other_hash) delete_if { |k,v| other_hash[k] == v } end end squares = { 1 => 1, 2 => 4, 3 => 9 } doubles = { 1 => 2, 2 => 4, 3 => 6 } squares.remove_hash(doubles) squares # => {1=>1, 3=>9}  To wipe out the entire contents of a Hash, use Hash#clear
  • 13. Using an array or other modifiable object as hash key  A naive solution tends to lose hash values once the keys are modified coordinates = [10, 5] treasure_map = { coordinates => 'jewels' } treasure_map[coordinates] # => "jewels“ # Add a z-coordinate to indicate how deep the treasure is buried. coordinates << -5 coordinates # => [10, 5, -5] treasure_map[coordinates] # => nil  The easiest solution is to call the Hash#rehash method every time you modify one of the hash's keys treasure_map.rehash treasure_map[coordinates] # => "jewels"
  • 14. Continued… module ReliablyHashable def hash return object_id end end class ReliablyHashableArray < Array include ReliablyHashable end coordinates = ReliablyHashableArray.new([10,5]) treasure_map = { coordinates => 'jewels' } treasure_map[coordinates] # => "jewels“ # Add a z-coordinate to indicate how deep the treasure is buried. coordinates.push(-5) treasure_map[coordinates] # => "jewels"
  • 15. Continued…  The implementation of hash given in the previous solution violates the principle that different representations of the same data should have the same hash code.  This means that two ReliablyHashableArray objects will have different hash codes even if they have the same contents a = [1,2] b = a.clone a.hash # => 11 b.hash # => 11 a = ReliablyHashableArray.new([1,2]) b = a.clone a.hash # => -606031406 b.hash # => -606034266  The solution is to freeze your hash keys.  Any frozen object can be reliably used as a hash key, since you can't do anything to a frozen object that would cause its hash code to change
  • 16. Keeping multiple values for the same hash key hash = Hash.new { |hash, key| hash[key] = [] } raw_data = [ [1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, ['b', 'c']], [3, 'c'] ] raw_data.each { |x,y| hash[x] << y } hash # => {1=>["a", "b", "c"], 2=>["a", ["b", "c"]], 3=>["c"]}  It's possible to subclass Hash to act like a normal hash until a key collision occurs, and then start keeping an array of values for the key that suffered the collision class MultiValuedHash < Hash def []=(key, value) if has_key?(key) super(key, [value, self[key]].flatten) else super end end end hash = MultiValuedHash.new raw_data.each { |x,y| hash[x] = y } hash # => {1=>["c", "b", "a"], 2=>["b", "c", "a"], 3=>"c"}
  • 17. Iterating over a hash  Note that each and each_pair return the key-value pairs in an apparently random order hash = { 1 => 'one', [1,2] => 'two', 'three' => 'three' } hash.each_pair { |key, value| puts "#{key.inspect} maps to #{value}"} # [1, 2] maps to two # "three" maps to three # 1 maps to one  Use Hash#each_key if you only need the keys of a hash active_toggles = { 'super' => true, 'meta' => true, 'hyper' => true } active_toggles.each_key { |active| puts active } # hyper # meta # super  Use Hash#each_value if you only need the values of a hash favorite_colors = { 'Alice' => :red, 'Bob' => :violet, 'Mallory' => :blue, 'Carol' => :blue, 'Dave' => :violet } summary = Hash.new(0) favorite_colors.each_value { |x| summary[x] += 1 } summary # => {:red=>1, :violet=>2, :blue=>2}
  • 18. Continued…  Don't modify the keyset of a hash during an iteration, or you'll get undefined results and possibly a RuntimeError 1.upto(100) { |x| hash[x] = true } hash.keys { |k| hash[k * 2] = true } # RuntimeError: hash modified during iteration  Using an array as intermediary hash = {1 => 2, 2 => 2, 3 => 10} hash.keys # => [1, 2, 3] hash.values # => [2, 2, 10] hash.to_a # => [[1, 2], [2, 2], [3, 10]]  Sorting the result of Hash#keys. extensions = { 'Alice' => '104', 'Carol' => '210', 'Bob' => '110' } extensions.keys.sort.each do |k| puts "#{k} can be reached at extension ##{extensions[k]}“ end # Alice can be reached at extension #104 # Bob can be reached at extension #110 # Carol can be reached at extension #210
  • 19. Continued…  Hash#sort and Hash#sort_by turn a hash into an array of two-element subarrays (one for each key-value pair), then sort the array of arrays however you like  This code sorts a to-do list by priority, then alphabetically to_do = { 'Clean car' => 5, 'Take kangaroo to vet' => 3, 'Realign plasma conduit' => 3 } to_do.sort_by { |task, priority| [priority, task] }.each { |k,v| puts k } # Realign plasma conduit # Take kangaroo to vet # Clean car  This code sorts a hash full of number pairs according to the magnitude of the difference between the key and the value transform_results = { 4 => 8, 9 => 9, 10 => 6, 2 => 7, 6 => 5 } by_size_of_difference = transform_results.sort_by { |x, y| (x-y).abs } by_size_of_difference.each { |x, y| puts "f(#{x})=#{y}: difference #{y-x}" } # f(9)=9: difference 0 # f(6)=5: difference -1 # f(10)=6: difference -4 # f(4)=8: difference 4 # f(2)=7: difference 5
  • 20. Iterating over a hash in insertion order  To iterate over a hash in the order in which the elements were added to the hash.  Use the orderedhash library require 'orderedhash‘ h = OrderedHash.new h[1] = 1 h["second"] = 2 h[:third] = 3 h.keys # => [1, "second", :third] h.values # => [1, 2, 3] h.each { |k,v| puts "The #{k} counting number is #{v}" } # The 1 counting number is 1 # The second counting number is 2 # The third counting number is 3  OrderedHash is a subclass of Hash that keeps an array of the keys in insertion order.  When you add key-value pair to the hash, OrderedHash modifies both the underlying hash and the array  But any operation that modifies an OrderedHash may also modify the internal array, so it's slower than just using a hash.  OrderedHash#delete is especially slow, since it must perform a linear search of the internal array to find the key being deleted.  Hash#delete runs in constant time, but OrderedHash#delete takes time proportionate to size of the hash.
  • 21. Inverting a hash  To switch the keys and values of hash phone_directory = { 'Alice' => '555-1212', 'Bob' => '555-1313', 'Mallory' => '111-1111' } phone_directory.invert # => {"111-1111"=>"Mallory", "555-1212"=>"Alice", "555-1313"=>"Bob"}  Hash#invert probably won't do what you want if your hash maps more than one key to the same value.  Only one of the keys for that value will show up as a value in the inverted hash phone_directory = { 'Alice' => '555-1212', 'Bob' => '555-1313', 'Carol' => '555-1313', 'Mallory' => '111-1111', 'Ted' => '555-1212' } phone_directory.invert # => {"111-1111"=>"Mallory", "555-1212"=>"Ted", "555-1313"=>"Bob"}  To preserve all the data from the original hash, write a version of invert that keeps an array of values for each key …
  • 22. Safe invert class Hash def safe_invert new_hash = {} self.each do |k,v| if v.is_a? Array v.each { |x| new_hash.add_or_append(x, k) } else new_hash.add_or_append(v, k) end end return new_hash end def add_or_append(key, value) if has_key?(key) self[key] = [value, self[key]].flatten else self[key] = value end end end phone_directory.safe_invert # => {"111-1111"=>"Mallory", "555-1212"=>["Ted", "Alice"], "555-1313"=>["Bob", "Carol"]} phone_directory.safe_invert.safe_invert # => {"Alice"=>"555-1212", "Mallory"=>"111-1111", "Ted"=>"555-1212", "Carol"=>"555-1313", "Bob"=>"555-1313"}
  • 23. Continued…  Ideally, if you called an inversion method twice you'd always get the same data you started with.  The safe_invert method does better than invert on this score, but it's not perfect.  If your original hash used arrays as hash keys, safe_invert will act as if you'd individually mapped each element in the array to the same value.  Call safe_invert twice, and the arrays will be gone.