how to work with array of hashes - ruby-on-rails

[[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
is the array
how can I get the value corresponding to particular value.
say High returns 5 in this.
or how to convert this array of hashes to an array so that searching becomes easy.
I tried:
find_all { |v| v['name'] == "Low" }
but it says:
cant convert String to Integer
please provide some guidance

How about making a single hash out of it for efficient querying?
arr.flatten.reduce(:merge)
#=> {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}

If you have some code like:
array = [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
Then turn it into an ruby hash:
hash = array.inject({}) {|h, e| h.merge(e.first) }
# => {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
So you can find 'Low' value easily :
hash['Low']
# => 3
EDIT: The answer of Mark Thomas is pretty great, and shorter than the inject since it does the same thing. He wrote it before I answered. Nice ;)

In the general case, the hashes won't be unique, so you need to filter rather than pick one via indexing. For example, let's say you have this:
arr = [[{:apple => 'abc'}], [{:banana => 'def'}], [{:coconut => 'ghi'}]]
# => [[{:apple=>"abc"}], [{:banana=>"def"}], [{:coconut=>"ghi"}]]
Now let's suppose you want to get the value corresponding to any hash with a :coconut key. Then just use:
arr.flatten.map { |h| h[:coconut] }.compact
# => ["ghi"]
That gives you the list of answers. In this case there's only one matching key, so there's only one entry in the array. If there were other hashes that had a :coconut key in there, then you'd have something like:
# => ["ghi", "jkl", "mno"]
On the whole, though, that's a very unusual data structure to have. If you control the structure, then you should consider using objects that can return you sensible answers in the manner that you'd like, not hashes.

You could simply call #flatten on the original array. That would give you an array of hashes. What I think you would really want is just one hash.
1.8.7 :006 > [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]].flatten
=> [{"Postponed"=>10}, {"Low"=>3}, {"Medium"=>4}, {"High"=>5}]
I would ask, what are you doing to get that original structure? Can that be changed?

How about this?
arr = [
[{"Postponed"=>10}],
[{"Low"=>3}],
[{"Medium"=>4}],
[{"High"=>5}]
]
arr1 = []
arr.each{|a|
arr1.push(a[0])
}
Although I wonder if you really just want to get one hash, which you'd do like so:
myHash = {}
arr.each{|a|
a[0].each{|b, c|
myHash[b] = c
}
}
You would then access it like myHash["Postponed"]

Related

Order Hash and delete first key-value pair

I have a Hash with timestamp as keys.
hash = {
"2016-05-31T22:30:58+02:00" => {
"path" => "/",
"method" => "GET"
},
"2016-05-31T22:31:23+02:00" => {
"path" => "/tour",
"method" => "GET"
},
"2016-05-31T22:31:05+02:00" => {
"path" => "/contact_us",
"method" => "GET"
}
}
I order the collection and get the first pair like this:
hash.sort_by {|k, _| k}.first.first
But how do I remove it?
The delete method requires you to know the exakt spelling of the key. Of course I could return the key and then use it in the delete method, but I was thinking if there was any more straight forward way?
The method you are looking for is hash.keys it returns an array of the keys:
hash.delete(hash.keys.min)
EDIT: I've updated the answer to reflect that keys must be sorted first, this has been added in the original question and brought up by #Shadwell in comments to this post.
I replaced hash.keys.sort.first for hash.keys.min as suggested by #Cary Swoveland, it is not only more performant but better semantically.
Also note that shift can be used on the Hash values.
hash.shift
Removes the first key-value pair from the hash. Works on arrays too.
As the OP has not stated that the hash's keys are in sorted order, we must assume that there is no guarantee that they are.
hash = { "2016-05-31T22:31:05+02:00"=>{ "path"=>"/tour", "method"=>"GET" },
"2016-05-31T22:30:58+02:00"=>{ "path"=>"/", "method"=>"GET" },
"2016-05-31T22:31:23+02:00"=>{ "path"=>"/contact_us", "method"=>"GET" } }
First, find the smallest key (the second one):
smallest_key = hash.keys.min
#=> "2016-05-31T22:30:58+02:00"
This is obviously more efficient than sorting the keys then taking the smallest.
Because the date-time strings are in iso8601 format, they can be sorted as strings, without having to first convert them to time objects.
Then use Hash#reject to obtain the desired hash:
hash.reject { |k,_| k == smallest_key }
#=> {"2016-05-31T22:31:05+02:00"=>{"path"=>"/tour", "method"=>"GET"},
# "2016-05-31T22:31:23+02:00"=>{"path"=>"/contact_us", "method"=>"GET"}}
To change hash in place, write
hash.delete(smallest_key }
hash

Nice array from pluck

I have a model and I love the pluck method I can use. If I do this:
#x = AwesomeModel.all.pluck(:column_one, :column_two)
then I get a multidimensional array: #x[][]. With my sad skills, I work with them using the numbers:
#x[0][1]
how can I can use pluck or a similar method to access the array something like this:
#x[0][:column_two]
If you are concerned about the structure of what you get back from the db, you should simply do:
#x = AwesomeModel.all.select(:column_one, :column_two)
Then you'd keep the fast db query advantage + have AwesomeModel instances, but with only column_one and column_two filled
Or if you desire to do it manually:
#x = AwesomeModel.all.pluck(:column_one, :column_two).map do |array|
OpenStruct.new({column_one: array[0], column_two: array[1] }) }
end
Then you can use it like a regular model:
#x[0].column_one
# or even
#x[0][:column_two]
You could do
class ActiveRecord::Base
def self.pluck_hash(*args)
plucked = pluck(*args)
plucked.map {|ary| Hash[args.zip ary]}
end
end
AwesomeModel.all.pluck_hash(:column_one, :column_two)
#=> [{:column_one => 'value', :column_two => 'value}, {...}, ... ]
First of all, don't use .all.pluck, because it returns an array of values, and that makes you loose all the advantages of ActiveRecord::Relation.
Instead use AwsomeModel.method directly, it would create the query but not run it until you need it, AwsomeModel.select(:column_1, :column_2) would create a
select (awesome_models.column_1, awsome_models.column_2)
query, and the result would be an array of ActiveRecord::Relation objects, which are still chainable, and values are still under keys of the column name eg:
AwsomeModel.select(:column_1, :column_2).first.column_1
Instead of
AwesomeModel.all.pluck(:column_1, :column_2).first[0] # or .first.first

How to remove nested keys from a hash list in Rails

I am now trying for some hours to remove a nested hash key of a hash list.
I saw many solution non-nested hashs wich looks like this:
sample_hash = {"key1" => "value1", "key2" => "value2"}
sample_hash.except("key1")
This results in:
{"key2"=>"value2"}
But if I try to use the except method on a hash with nested key then it doesn't work.
Here my code:
nested_hash = {"key1"=>"value1", "key2"=>{
"nested_key1"=>"nestedvalue1",
"nested_key2"=>"nestedvalue2"
}
}
nested_hash.except("nested_key2")
The except() method returns the nested_hash without any changes. I have looked for a solution how I can pass nested hash-keys to the except method, but couldn't find anything. Is it even possible to pass nested keys to this method or should I use some other method which deletes a nested hash key from my hash list?
what about
Hash[nested_hash.map {|k,v| [k,(v.respond_to?(:except)?v.except("nested_key2"):v)] }]
=> {"key1"=>"value1", "key2"=>{"nested_key1"=>"nestedvalue1"}}
ugh.
The accepted solution is valid for the scenario given but if you're looking for something that will do this for arbitrarily nested hash tables then you're going to need a recursive solution. I couldn't find a suitable solution anywhere, so I wrote one here.
Reproduced here with annotations:
class Hash
def except_nested(key)
r = Marshal.load(Marshal.dump(self)) # deep copy the hashtable
r.except_nested!(key)
end
def except_nested!(key)
self.except!(key)
self.each do |_, v| # essentially dfs traversal calling except!
v.except_nested!(key) if v.is_a?(Hash)
end
end
end
adding it to the Hash class so that you can call it the same way you call except/except! anywhere else.
t = { a: '1', b: { c: '3', d: '4' } }
r = t.except_nested(:c)
# r => {:a=>"1", :b=>{:d=>"4"}}
# t => {:a=>"1", :b=>{:c=>"3", :d=>"4"}}
t.except_nested!(:c)
# t => {:a=>"1", :b=>{:d=>"4"}}
try
my_hash = Hash[nested_hash.map {|k,v| {k=>v.is_a? Array ? v.except("nested_key2") : v}}.map {|key, value| [key, value]}]
But this seems wrong, I wish I never started down this path, I'm willing to bet there is an easier way!
If you know that the nested key will always be there then you can just do
nested_hash['key2'].except!('nested_key2')
the whole nested_hash will now be lacking 'nested_key2'

How to check if specific value is present in a hash?

I'm using Rails and I have a hash object. I want to search the hash for a specific value. I don't know the keys associated with that value.
How do I check if a specific value is present in a hash? Also, how do I find the key associated with that specific value?
Hash includes Enumerable, so you can use the many methods on that module to traverse the hash. It also has this handy method:
hash.has_value?(value_you_seek)
To find the key associated with that value:
hash.key(value_you_seek)
This API documentation for Ruby (1.9.2) should be helpful.
The simplest way to check multiple values are present in a hash is:
h = { a: :b, c: :d }
h.values_at(:a, :c).all? #=> true
h.values_at(:a, :x).all? #=> false
In case you need to check also on blank values in Rails with ActiveSupport:
h.values_at(:a, :c).all?(&:present?)
or
h.values_at(:a, :c).none?(&:blank?)
The same in Ruby without ActiveSupport could be done by passing a block:
h.values_at(:a, :c).all? { |i| i && !i.empty? }
Hash.has_value? and Hash.key.
Imagine you have the following Array of hashes
available_sports = [{name:'baseball', label:'MLB Baseball'},{name:'tackle_football', label:'NFL Football'}]
Doing something like this will do the trick
available_sports.any? {|h| h['name'] == 'basketball'}
=> false
available_sports.any? {|h| h['name'] == 'tackle_football'}
=> true
While Hash#has_key? works but, as Matz wrote here, it has been deprecated in favour of Hash#key?.
Hash's key? method tells you whether a given key is present or not.
hash.key?(:some_key)
The class Hash has the select method which will return a new hash of entries for which the block is true;
h = { "a" => 100, "b" => 200, "c" => 300 }
h.select {|k,v| v == 200} #=> {"b" => 200}
This way you'll search by value, and get your key!
If you do hash.values, you now have an array.
On arrays you can utilize the Enumerable search method include?
hash.values.include?(value_you_seek)
An even shorter version that you could use would be hash.values

How do you iterate over active record objects in Ruby On Rails?

This question is quite simple but I have run into the problem a few times.
Let's say you do something like:
cars = Vehicle.find_by_num_wheels(4)
cars.each do |c|
puts "#{c.inspect}"
end
This works fine if cars is an array but fails if there is only one car in the database. Obviously I could do something like "if !cars.length.nil?" or check some other way if the cars object is an array before calling .each, but that is a bit annoying to do every time.
Is there something similar to .each that handles this check for you? Or is there an easy way to force the query result into an array regardless of the size?
You might be looking for
cars = Vehicle.find_all_by_num_wheels(4)
The dynamic find_by_ methods only return one element and you have to use find_all_by_ to return multiple.
If you always want all of the cars, you should use find_all instead:
cars = Vehicle.find_all_by_num_wheels(4)
You could also turn a single Vehicle into an array with:
cars = [cars] unless cars.respond_to?(:each)
Named scoped version for your problem
Vehicle.scoped(:conditions => { :num_wheels => 4 } ).each { |car| car.inspect }
You can do this to get arrays everytimes :
cars = Vehicle.find(:all, :conditions => {num_wheels => 4})
I don't think that you have a loop that will check if the object is an array.
Another solution could be:
for i in (1..cars.lenght)
puts cars[i].inspect
end
(haven't tested, it might break to test the lenght on a string. Let me know if it does)

Resources