Converting Ruby array into a hash

2019-03-07 17:30发布

I am attempting to write a method named my_transform that takes an array as follows:

items = ["Aqua", "Blue", "Green", "Red", "Yellow"]

and displays the items' indexes as follows:

item_to_position = {"Aqua"=>0, "Blue"=>1, "Green"=>2, "Red"=>3, "Yellow"=>4}

I should be able to execute:

my_transform(items) == item_to_position

and receive true.

I have contemplated using each_with_index. Should I begin by saying:

items = ["Aqua", "Blue", "Green", "Red", "Yellow"]

hash = Hash[*array]

def my_transform

I have to convert the string to a hash. Any help is appreciated.

标签: arrays ruby hash
6条回答
做个烂人
2楼-- · 2019-03-07 18:15
items = ["Aqua", "Blue", "Green", "Red", "Yellow"]
def my_transform(items)     
   Hash[items.each_with_index.map { |value, index| [value, index] }]
end
查看更多
爷的心禁止访问
3楼-- · 2019-03-07 18:16

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 includes 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.

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.

查看更多
放荡不羁爱自由
4楼-- · 2019-03-07 18:16
def my_transform(arr)
  arr.inject({}) {|m,e| m[e] = arr.index(e); m }
end
查看更多
对你真心纯属浪费
5楼-- · 2019-03-07 18:19

Most Rubies

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}

Ruby >= 2.1.0

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}
查看更多
女痞
6楼-- · 2019-03-07 18:21

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}
查看更多
贼婆χ
7楼-- · 2019-03-07 18:25

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
查看更多
登录 后发表回答