I am attempting to write a method named my_transform
that takes an array as follows:
items = [\"Aqua\", \"Blue\", \"Green\", \"Red\", \"Yellow\"]
This works at least as far back as Ruby 1.9.3.
# Verbose, but flexible!
def hasherize *array
hash = {}
array.flatten!
array.each_with_index { |key, value| hash[key] = value }
hash
end
# Pass a single array as an argument.
hasherize %w(Aqua Blue Green Red Yellow)
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
# Pass multiple arguments to the method.
hasherize :foo, :bar, :baz
#=> {:foo=>0, :bar=>1, :baz=>2}
If you're running a recent Ruby, you can simplify the above to:
def hasherize *array
array.flatten.each_with_index.to_h
end
The results will be the same as above, but the Array#to_h method simplifies the code a lot. However, you still need to flatten the array to avoid results like:
#=> {["Aqua", "Blue", "Green", "Red", "Yellow"]=>0}
I would use Array#to_h:
items = ["Aqua", "Blue", "Green", "Red", "Yellow"]
items.each_with_index.to_h
#=> { "Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4 }
Note that to_h
was introduced in Ruby 2.1
Using to_h
your my_transform
method could look like this:
def my_transform(items)
items.each_with_index.to_h
end
def my_transform(arr)
arr.inject({}) {|m,e| m[e] = arr.index(e); m }
end
items = ["Aqua", "Blue", "Green", "Red", "Yellow"]
def my_transform(items)
Hash[items.each_with_index.map { |value, index| [value, index] }]
end
You can do this in various ways.
Create an array and convert it to a hash
Until fairly recently, you would use the public class method Hash::[] to convert an array to a hash. It works like this:
h = Hash[ [[:a, 1], [:b, 2]] ]
#=> {:a=>1, :b=>2}
or
h = Hash[:a, 1, :b, 2]
#=> {:a=>1, :b=>2}
In Ruby v2.1.0 the methods Array#to_h and Enumerable#to_h were introduced. The first works like this:
h = [[:a, 1], [:b, 2]].to_h
#=> {:a=>1, :b=>2}
Therefore, to use Hash
or to_h
you must first create the array:
arr1 = [["Aqua", 0], ["Blue", 1], ["Green", 2], ["Red", 3], ["Yellow", 4]]
or
arr2 = ["Aqua", 0, "Blue", 1, "Green", 2, "Red", 3, "Yellow", 4]
In the second case we'd use it like this:
Hash[*arr2]
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
Let's first create arr1
. You are right that you need to use Enumerable#each_with_index. You then need to use Enumerable#to_a to convert each element of items
to an array [<color>, index]
.
items = ["Aqua", "Blue", "Green", "Red", "Yellow"]
arr = items.each_with_index.to_a
#=> [["Aqua", 0], ["Blue", 1], ["Green", 2], ["Red", 3], ["Yellow", 4]]
Let's look at this more closely:
enum = items.each_with_index
#=> #<Enumerator: ["Aqua", "Blue", "Green", "Red", "Yellow"]:each_with_index>
enum
, an enumerator, is an instance of the class Enumerator. The Enumerator
class is one of many classes that include
s the Enumerable
module, of which to_a
is an instance method. Not only does:
arr = enum.to_a
#=> [["Aqua", 0], ["Blue", 1], ["Green", 2], ["Red", 3], ["Yellow", 4]]
convert the enumerator to the desired array, but it is a convenient way to view the elements of any enumerator (which are generally passed to either a block or to another enumerator).
So we can now create the hash:
h = Hash[arr]
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
or
h = Hash[*arr.flatten]
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
or
h = arr.to_h
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
Suppose now that we had:
items = ["Aqua", "Blue", "Green", "Aqua", "Aqua"]
We then obtain:
items.each_with_index.to_a.to_h
#=> {"Aqua"=>4, "Blue"=>1, "Green"=>2}
In building the hash, Ruby first creates the key-value pair "Aqua"=>0
, which she later overwrites with "Aqua"=>3
and then with "Aqua"=>4
. This is a consequence of the fact that hashes have unique keys.
Build the hash from scratch
Now suppose we start with an empty hash:
h = {}
(same as h = Hash.new
) and add key-value pairs:
items = ["Aqua", "Blue", "Green", "Red", "Yellow"]
items.each_index { |i| h[items[i]] = i }
#=> ["Aqua", "Blue", "Green", "Red", "Yellow"]
h #=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
We could alternatively write:
items.size.times { |i| h[items[i]] = i }
#=> 5
h #=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
or
(0...items.size).each { |i| h[items[i]] = i }
#=> 0...5
h #=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
The Ruby way is skip the step h = {}
and to use each_with_index
, as before, together with Enumerator#with_object:
items.each_with_index.with_object({}) { |(s,i),h| h[s] = i }
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
The "object" in with_object
is a hash, with_object
's argument being its initial value, here an empty hash. This object is represented by the block variable h
and is returned after all elements of items
have been enumerated (so we don't need a subsequent line h
to return the hash).
Lets look at the steps that are performed here. First, we have
enum0 = items.each_with_index
#=> #<Enumerator: ["Aqua", "Blue", "Green", "Red", "Yellow"]:each_with_index>
which I discussed earlier. Then Ruby computes
enum1 = enum0.with_object({})
#=> #<Enumerator: #<Enumerator: ["Aqua", "Blue", "Green", "Red", "Yellow"]
:each_with_index>:with_object({})>
Examine the return value carefully. As you see, enum1
, like enum0
, is an enumerator. You might think of it as a "compound enumerator". To see the values of enum1
that will be passed to the block, you can convert it to an array:
enum1.to_a
#=> [[["Aqua", 0], {}], [["Blue", 1], {}], [["Green", 2], {}],
# [["Red", 3], {}], [["Yellow", 4], {}]]
As you see, enum1
has five elements, each an array containing an array and a hash. The elements of enum1
are passed to the block by Enumerator#each, (which calls Array#each):
enum1.each { |(s,i),h| h[s] = i }
#=> {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}
We can use Enumerator#next to pass each element of enum1
to the block, and set the block variables to its value. The first is:
(s,i),h = enum1.next
#=> [["Aqua", 0], {}]
s #=> "Aqua"
i #=> 0
h #=> {}
Notice how [["Aqua", 0], {}]
is decomposed into its three constituent elements and each block variable is set equal to one of the elements. This is called array decomposition.
We can now perform the block calculation:
h[s] = i
#=> {}["Aqua"] = 0
so now:
h #=> {"Aqua"=>0}
Then the second element is passed to the block:
(s,i),h = enum1.next
#=> [["Blue", 1], {"Aqua"=>0}]
s #=> "Blue"
i #=> 1
h #=> {"Aqua"=>0}
Notice how h
has been updated. The block calculation is now:
h[s] = i
#=> {"Aqua"=>0}["Blue"] = 1
and now:
h #=> {"Aqua"=>0, "Blue"=>1}
The remaining calculations are performed similarly. After all elements of enum1
have been enumerated, enum1.each
returns h
.
You can also try this.
e.g.
items = ["Aqua", "Blue", "Green", "Red", "Yellow"]
items.inject({}) do |tmphash, (k,v)|
tmphash[k] = items.index(k)
tmphash
end
## OUTPUT
{"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}