Cleanest way to create a Hash from an Array
There is already a method in ActiveSupport that does this.
['an array', 'of active record', 'objects'].index_by(&:id)
And just for the record, here's the implementation:
def index_by
inject({}) do |accum, elem|
accum[yield(elem)] = elem
accum
end
end
Which could have been refactored into (if you're desperate for one-liners):
def index_by
inject({}) {|hash, elem| hash.merge!(yield(elem) => elem) }
end
Create a hash out of an array where the values are the indices of the elements
I'm adding my two cents:
array = [1,3,4,5,6,6,6,8,8,8,9,7,7,7]
hash = {}
array.map.with_index {|val, idx| [val, idx]}.group_by(&:first).map do |k, v|
hash[k] = v[0][1] if v.size == 1
hash[k] = v.map(&:last) if v.size > 1
end
p hash #=> {1=>0, 3=>1, 4=>2, 5=>3, 6=>[4, 5, 6], 8=>[7, 8, 9], 9=>10, 7=>[11, 12, 13]}
It fails with duplicated element not adjacent, of course.
This is the expanded version, step by step, to show how it works.
The basic idea is to build a temporary array with pairs of value and index, then work on it.
array = [1,3,4,5,6,6,6]
tmp_array = []
array.each_with_index do |val, idx|
tmp_array << [val, idx]
end
p tmp_array #=> [[1, 0], [3, 1], [4, 2], [5, 3], [6, 4], [6, 5], [6, 6]]
tmp_hash = tmp_array.group_by { |e| e[0] }
p tmp_hash #=> {1=>[[1, 0]], 3=>[[3, 1]], 4=>[[4, 2]], 5=>[[5, 3]], 6=>[[6, 4], [6, 5], [6, 6]]}
hash = {}
tmp_hash.map do |k, v|
hash[k] = v[0][0] if v.size == 1
hash[k] = v.map {|e| e[1]} if v.size > 1
end
p hash #=> {1=>1, 3=>3, 4=>4, 5=>5, 6=>[4, 5, 6]}
It can be written as one line as:
hash = {}
array.map.with_index.group_by(&:first).map { |k, v| v.size == 1 ? hash[k] = v[0][1] : hash[k] = v.map(&:last) }
p hash
Creating a hash from an array of strings and arrays
You can use Hash[...]
, here is an example:
=> array = ["temp-1", [69.63076923076923, 5.397898451463096], "temp-2", [70.44,
0.9044335243676014], "hum-1", [45.199999999999996,
0.09999999999999788], "hum-2", [43.82, 1.0568822072492268]]
=> Hash[*array]
#> {"temp-1"=>[69.63076923076923, 5.397898451463096], "temp-2"=>[70.44, 0.9044335243676014], "hum-1"=>[45.199999999999996, 0.09999999999999788], "hum-2"=>[43.82, 1.0568822072492268]}
What is the best way to convert an array to a hash in Ruby
NOTE: For a concise and efficient solution, please see Marc-André Lafortune's answer below.
This answer was originally offered as an alternative to approaches using flatten, which were the most highly upvoted at the time of writing. I should have clarified that I didn't intend to present this example as a best practice or an efficient approach. Original answer follows.
Warning! Solutions using flatten will not preserve Array keys or values!
Building on @John Topley's popular answer, let's try:
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]
This throws an error:
ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10
The constructor was expecting an Array of even length (e.g. ['k1','v1,'k2','v2']). What's worse is that a different Array which flattened to an even length would just silently give us a Hash with incorrect values.
If you want to use Array keys or values, you can use map:
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
This preserves the Array key:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
Hashing the arrangement of an array
An equivalent hash goes something like this for [ 40, 20, 10, 30 ]
- 40 is greater than 3 of the subsequent values
- 20 is greater than 1 of the subsequent values
- 10 is greater than 0 of the subsequent values
- 30 has nothing after it, so ignore it.
That is a pair nested loop of time Order(N^2). (Actually about 4*4/2, where there are 4 items.) Just a few lines of code.
Pack 3,1,0, either the way you did it or with anatolyg's slightly tighter:
3 * 3! +
1 * 2! +
0 * 1!
which equal 20. It needs to be stored in the number of bits needed for 4!, namely 5 bits.
I'm pretty sure this is optimal for space. I have not thought of a faster way to compute it other than O(N^2).
How big is your N? For N=100, you need about 520 bits and 5K operations. 5K operations might take several microseconds (for C++), and probably less than a millisecond (even for an interpreted language).
Create Hash iterating an array of objects
Use Hash[]
:
Forecast = Struct.new(:name, :data)
forecasts = [Forecast.new('bob', 1), Forecast.new('mary', 2)]
Hash[forecasts.map{|forecast| [forecast.name, forecast.data]}]
# => {"mary"=>2, "bob"=>1}
Related Topics
What Are the Ruby's Object#Taint and Object#Trust Methods
How to Check If a Number Is Included in a Range (In One Statement)
How to Log the Entire Trace Back of a Ruby Exception Using the Default Rails Logger
Using Question Mark Character in Rails/Activerecord Column Name
How to Set a Blank Value for an F.Select Form Field
Iterate Every Month with Date Objects
Errno::Enoent: No Such File or Directory Ruby
What's the Most Efficient Way to Deep Copy an Object in Ruby
Best Practice About Empty Belongs_To Association
Adding an Instance Variable to a Class in Ruby
How to Get an Array with Column Names of a Table
How to Send Mail with Rails Without a Template