Shorthand way to take an array of Ruby models and turn it into a hash with the id as the key? - ruby

I have an array of models that I would like to turn into a hash so I can reference them by id. I know I can iterate over the items and put them in a hash, but I know there must be a quick, shorthand way of doing this same thing:
my_models_hash = {}
#my_models.each do |model|
my_models_hash[model.id] = model
end
How can I do this same thing in one, short line?

You're after each_with_object.
my_models_hash = #my_models.each_with_object({}) { |m,h| h[m.id] = m }

One way:
#my_models.map { |m| [m.id, m] }.to_h
Prior to v2.0, this would have to be written:
Hash[#my_models.map { |m| [m.id, m] }]

If you're using Rails (or more specifically ActiveSupport), there's Enumerable#index_by:
#my_models.index_by(&:id)
#=> { 1 => #<Model id: 1, ...>, 2 => #<Model id: 2, ...>, ...}

Related

Ruby change the order of hash keys

I have a hash and I would like the change the key order from.
{"result"=>{"data"=>[{"Quantity"=>13, "Rate"=>17.1},
{"Quantity"=>29,"Rate"=>3.2},
{"Quantity"=>7, "Rate"=>3.4}]}}
To:
{"result"=>{"data"=>[{"Rate"=>17.1, "Quantity"=>13},
{"Rate"=>3.2, "Quantity"=>29},
{"Rate"=>3.4, "Quantity"=>7}]}}
that can be accessed by hash["result"]["data"]. I tried;
hash["result"]["data"][0].each_value{|v| v.replace({"Rate" => v.delete("Rate")}.merge(v))}
But it gives error:
NoMethodError (undefined method `delete' for
17.1:Float):
Try this,
hash["result"]["data"].each{|v| v.replace({"Rate" => v.delete("Rate")}.merge(v))}
I think their is no need to do this much of operations. I suppose data contains your whole hash then just one map and reverse of hash will resolve your problem.
data['result']['data'] = data['result']['data'].map{|v| Hash[v.to_a.reverse]}
Four more ways...
Reverse the order of those hash items:
hash['result']['data'].map! { |h| h.to_a.reverse.to_h }
Move "Quantity" to the end:
hash['result']['data'].each { |h| h["Quantity"] = h.delete("Quantity") }
Move the first item to the end:
hash['result']['data'].map! { |h| h.merge([h.shift].to_h) }
Force a certain given order:
keys = ["Rate", "Quantity"]
hash['result']['data'].map! { |h| keys.zip(h.values_at(*keys)).to_h }
hash = {a: 1, b: 2, c: 3}
hash.slice!(:b, :a)
puts hash # { :b => 2, :a => 1 }

ruby how to make a hash with new keys, and values from an array

I have an array of arrays like this:
arr = [["food", "eggs"],["beverage", "milk"],["desert", "cake"]]
And I need to turn it into an array of hashes where the keys are custom and new, and the values of the keys are the values in the array, like this:
hash = [{"category": "food", "item":"eggs"},
{"category": "beverage", "item":"milk"}
{"category": "desert", "item":"cake"}]
how would I do this?
thank you
Use Array#map:
arr = [["food", "eggs"], ["beverage", "milk"], ["desert", "cake"]]
arr.map { |category, item| { category: category, item: item } }
# => [
# {:category=>"food", :item=>"eggs"},
# {:category=>"beverage", :item=>"milk"},
# {:category=>"desert", :item=>"cake"}
# ]
arr = [["food", "eggs"],["beverage", "milk"],["desert", "cake"]]
arr.inject([]) do |hash, (v1, v2)|
hash << { category: v1, item: v2 }
end
I used inject to keep the code concise.
Next time you may want to show what you have tried in the question, just to demonstrate that you actually tried to do something before asking for code.
hash = array.map {|ary| Hash[[:category, :item].zip ary ]}
hash = arr.each_with_object({}){|elem, hsh|hsh[elem[0]] = elem[1]}

Ruby: cleaner returns from loop iteration methods

I find that I frequently have methods that iterate through an enumerable in order to return a different enumerable or a hash. These methods almost always look like this simplistic example:
def build_hash(array)
hash = {}
array.each do |item|
hash[ item[:id] ]= item
end
hash
end
This approach works works, but I've often wondered if there's a cleaner way to do this, specifically without having to wrap the loop in a temporary object so that the return is correct.
Does anyone know of an improved and/or cleaner and/or faster way to do this, or is this pretty much the best way?
Here are a few ways, considering your specific example
arr = [{:id => 1, :name => :foo}, {:id => 2, :name => :bar}]
Hash[arr.map{ |o| [o[:id], o] }]
arr.each_with_object({}){ |o, h| h[o[:id]] = o }
arr.reduce({}){ |h, o| h[o[:id]] = o; h }
arr.reduce({}){ |h, o| h.merge o[:id] => o }
# each of these return the same Hash
# {1=>{:id=>1, :name=>:foo}, 2=>{:id=>2, :name=>:bar}}
Well in this case, you can use inject and do something like this :
def build_hash(array)
array.inject({}) { |init, item| init[item[:id]] = item; init }
end
{}.tap { |h| array.each { |a| h[a[:id]] = a } }
Here is also a way how to convert Array into Hash.
list_items = ["1", "Foo", "2", "Bar", "3" , "Baz"]
hss = Hash[*list_items]
parameters must be even, otherwise a fatal error is raised, because an odd
number of arguments can’t be mapped to a series of key/value pairs.
{"1"=>"Foo", "2"=>"Bar", "3"=>"Baz"}
You can use ActiveSupport's index_by.
Your example becomes trivial:
def build_hash(array)
array.index_by{|item| item[:id]}
end
There is no really great way to build a hash in Ruby currently, even in Ruby 2.0.
You can use Hash[], although I find that very ugly:
def build_hash(array)
Hash[array.map{|item| [item[:id], item]}]
end
If we can convince Matz, you could at least:
def build_hash(array)
array.map{|item| [item[:id], item]}.to_h
end
There are other requests for new ways to create hashes.

Convert array-of-hashes to a hash-of-hashes, indexed by an attribute of the hashes

I've got an array of hashes representing objects as a response to an API call. I need to pull data from some of the hashes, and one particular key serves as an id for the hash object. I would like to convert the array into a hash with the keys as the ids, and the values as the original hash with that id.
Here's what I'm talking about:
api_response = [
{ :id => 1, :foo => 'bar' },
{ :id => 2, :foo => 'another bar' },
# ..
]
ideal_response = {
1 => { :id => 1, :foo => 'bar' },
2 => { :id => 2, :foo => 'another bar' },
# ..
}
There are two ways I could think of doing this.
Map the data to the ideal_response (below)
Use api_response.find { |x| x[:id] == i } for each record I need to access.
A method I'm unaware of, possibly involving a way of using map to build a hash, natively.
My method of mapping:
keys = data.map { |x| x[:id] }
mapped = Hash[*keys.zip(data).flatten]
I can't help but feel like there is a more performant, tidier way of doing this. Option 2 is very performant when there are a very minimal number of records that need to be accessed. Mapping excels here, but it starts to break down when there are a lot of records in the response. Thankfully, I don't expect there to be more than 50-100 records, so mapping is sufficient.
Is there a smarter, tidier, or more performant way of doing this in Ruby?
Ruby <= 2.0
> Hash[api_response.map { |r| [r[:id], r] }]
#=> {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}}
However, Hash::[] is pretty ugly and breaks the usual left-to-right OOP flow. That's why Facets proposed Enumerable#mash:
> require 'facets'
> api_response.mash { |r| [r[:id], r] }
#=> {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}}
This basic abstraction (convert enumerables to hashes) was asked to be included in Ruby long ago, alas, without luck.
Note that your use case is covered by Active Support: Enumerable#index_by
Ruby >= 2.1
[UPDATE] Still no love for Enumerable#mash, but now we have Array#to_h. It creates an intermediate array, but it's better than nothing:
> object = api_response.map { |r| [r[:id], r] }.to_h
Something like:
ideal_response = api_response.group_by{|i| i[:id]}
#=> {1=>[{:id=>1, :foo=>"bar"}], 2=>[{:id=>2, :foo=>"another bar"}]}
It uses Enumerable's group_by, which works on collections, returning matches for whatever key value you want. Because it expects to find multiple occurrences of matching key-value hits it appends them to arrays, so you end up with a hash of arrays of hashes. You could peel back the internal arrays if you wanted but could run a risk of overwriting content if two of your hash IDs collided. group_by avoids that with the inner array.
Accessing a particular element is easy:
ideal_response[1][0] #=> {:id=>1, :foo=>"bar"}
ideal_response[1][0][:foo] #=> "bar"
The way you show at the end of the question is another valid way of doing it. Both are reasonably fast and elegant.
For this I'd probably just go:
ideal_response = api_response.each_with_object(Hash.new) { |o, h| h[o[:id]] = o }
Not super pretty with the multiple brackets in the block but it does the trick with just a single iteration of the api_response.

How to create an array out of certain values in hashes, which are contained in an array

I have an array of hashes:
a = [{name: "ben", sex: "m"},{name: "sarah", sex: "f"}]
What is the easiest way to create an array out of this with just the names? So I end up with:
b = ["ben", "sarah"]
I know you can do the following, but just wondering if there's a shortcut
b = []
a.each do |x|
b << x[:name]
end
Thanks for reading.
b = a.map { |hash| hash[:name] }
This is pretty basic Ruby, take a look at the Enumerable module and study all the methods carefully. [edit] Some random links about the topic: 1, 2, 3.

Resources