Skip to content
Frank Koehl edited this page Aug 9, 2018 · 6 revisions

Extensions to Array

Ruby's to_h method converts an array to a hash by interpreting the array as an array of [key, value] pairs. But what if you have a one-dimensional array of things that you want to push into a hash, and the values (or keys) are yet to be determined? Finishing Moves provides a more flexible implementation.

Array#to_hash_values

to_hash_values(starting_key = 0, &block)
Alias: to_hash_as_values

Convert an array of things into a hash with the array elements stored as values. By default the hash will be numerically indexed starting from zero.

sages = ['Rauru', 'Saria', 'Darunia', 'Princess Ruto', 'Impa', 'Nabooru', 'Zelda']

sages_hash = sages.to_hash_values
# => {0=>"Rauru", 1=>"Saria", 2=>"Darunia", 3=>"Princess Ruto", 4=>"Impa", 5=>"Nabooru", 6=>"Zelda"}

starting_key represents where key indexing should start. Unless a block is provided, keys are assumed to be numerical and will increment by one. The above example is equivalent to sages_hash = sages.to_hash_values(0).

The block syntax allows you to easily increment at any rate.

sages_hash = sages.to_hash_values(0) { |key| key + 3 }
# => {0=>"Rauru", 3=>"Saria", 6=>"Darunia", 9=>"Princess Ruto", 12=>"Impa", 15=>"Nabooru", 18=>"Zelda"}

Using the block syntax you can create keys out of almost anything, making to_hash_values a powerful tool for generating collections of objects.

class SageElements

  def initialize
    @keys = {
      :first  => :light,
      :light  => :forest,
      :forest => :fire,
      :fire   => :water,
      :water  => :shadow,
      :shadow => :spirit,
      :spirit => :time,
      :time   => :first,
    }
  end

  def first_key
    @keys[:first]
  end

  def next_key(pointer)
    @keys[pointer]
  end

end

sages_hash = sages.to_hash_values(elements.first_key) do |key|
  elements.next_key(key)
end
# => {:light=>"Rauru", :forest=>"Saria", :fire=>"Darunia", :water=>"Princess Ruto", :shadow=>"Impa", :spirit=>"Nabooru", :time=>"Zelda"}

Array#to_indexed_hash

to_indexed_hash(starting_key = 0)
Alias: to_hash_as_keys

Same logic as to_hash_values, but assumes an integer key, increments by 1, and skips the block syntax. It will raise an ArgumentError if the key is not of type Integer (floating point keys must use to_hash_values syntax).

sages.to_indexed_hash(22)
# => {22=>"Rauru", 23=>"Saria", 24=>"Darunia", 25=>"Princess Ruto", 26=>"Impa", 27=>"Nabooru", 28=>"Zelda"}

sages.to_indexed_hash("e")
# => ArgumentError: "e" is not an integer

Array#to_hash_keys

to_hash_keys(starting_value = 0, &block)

Convert an array of things into a hash, with the array values becoming keys. starting_value will be set as the value for each pair in the new array.

sages = ['Rauru', 'Saria', 'Darunia', 'Princess Ruto', 'Impa', 'Nabooru', 'Zelda']

sages_hash = sages.to_hash_keys
# => {"Rauru"=>0, "Saria"=>0, "Darunia"=>0, "Princess Ruto"=>0, "Impa"=>0, "Nabooru"=>0, "Zelda"=>0}

Note that the default starting_value is a numerical zero rather than nil deliberately. Ruby reports an undefined key as nil, so a non-nil value ensures each hash pair is fully "existent" in Ruby terms.

The block syntax allows for complex definitions of the value. This logic works precisely the same as to_hash_values, so see above for details.

Array#to_sym_strict

['foo', 'bar'].to_sym_strict

Take an array of string values and convert them to symbols, no block necessary!

test = ['FOO', 'bar', :baz]

test.to_sym_strict
# => [:FOO, :bar, :baz]
test
# => ['FOO', 'bar', :baz]

Note the :baz in there. Array#to_sym_strict utilizes String#to_sym under the hood, and so behaves exactly as you would suspect.

There's a Array#to_sym_strict! bang variant that replaces each element.

test = ['FOO', 'bar', :baz]

test.to_sym_strict!
# => [:FOO, :bar, :baz]
test
# => [:FOO, :bar, :baz]

Array#to_sym_loose

['foo', 'bar', 1, {a: :hash} ].to_sym_loose

Same behavior as Array#to_sym_strict, however we silently bypass any values do not have a #to_sym method defined. This allows us to perform string-to-symbol conversions on a hash of mixed types without fuss.

test = ['String1', 'string2', :baz, 1, {bat: 'bat'}, /hay/]
test.to_sym_strict
# => NoMethodError: undefined method `to_sym' for 1:Fixnum
test.to_sym_loose
# => [:String1, :string2, :baz, 1, {bat: 'bat'}, /hay/]
test
# => ['String1', 'string2', :baz, 1, {bat: 'bat'}, /hay/]

Bang variant Array#to_sym_loose! will perform replacements on the original array, ignoring anything that lacks a #to_sym_strict call.

Why didn't you just call it Array#to_sym?

Many programs rely on method reflection to make processing determinations. Defining a global Array#to_sym method would break anything that does stuff like if var.respond_to?(:to_sym).

Clone this wiki locally