Better way to search array in Ruby? - ruby-on-rails

I have an array of hashes:
arr = [{"id"=>"1", "name"=>"Alan"}, {"id"=>"2", "name"=>"Ben"}, {"id"=>"3", "name"=>"Carl"}, {"id"=>"4", "name"=>"Danny"}, {"id"=>"5", "name"=>"Eva"}]
If I were to find the name of id #4:
arr.find{ |a| a["id"] == "4" }["name"]
returns "Danny", which is what I want.
My question is, is there a shorter, more elegant way of accomplish the same search?

If you only need to look up one id, a linear search as you are doing is fine.
If you are doing many lookups on the same array, create a hash from the array, so the individual lookups are O(1)
h = Hash[arr.map{|a|[a["id"], a["name"]]}]
h["4"]

It is a perfectly reasonable way of doing it. The only problem I see is that your data structure is not lookup friendly. You need to search in array, instead of looking up by ID in hash. Searching in array is O(N) and is relatively more difficult code. Looking up in hash is O(1) and is no code at all.
So, I would convert your array to hash, and then look up in it. Especially, if you are planning to do many lookups.
people = arr.inject({}) {|memo,v| memo[v["id"].to_i]=v["name"]; memo}
people[4] # will return Danny
people[3] # will return Carl
Hope it helps!

Related

How to query to return the most common foreign key in a join table with rails

I have 3 models. Project, ProjectMaterial, and Material
A Project has_many ProjectMaterials and many Materials through ProjectMaterials.
This is bidirectional, with ProjectMaterial acting as a join table with user-submittable attributes.
I'd like to query the ProjectMaterial model to find the most frequent value of material_id. This way I can use it to find the most frequently used material.
Any help with a query would be greatly appreciated. I'm stuck. Thanks in advance!
You can chain group, count and sort methods on your ActiveRecord query like this:
ProjectMaterial.group(:material_id).count.values.sort.last
The first part ProjectMaterial.group(:material_id).count gives you the hash of each {material_id0 => rows_count0, material_id1 => rows_count1, ...}. Then, you can just get the values of the hash in an array, sort it and get the last item.
One way could be pluck ids to get the array, then count the most frequent.
ids = ProjectMaterial.pluck[:material_id]
For example: Ruby: How to find item in array which has the most occurrences?
Or better, by query to get a hash with counts:
counts = ProjectMaterial.group(:material_id).count
Once you know that you get a hash, you can sort by any ruby method, picking the most frequent or the n most frequent. Example of sorting:
counts.sort_by { |_, v| v }

In Rails, how do I perform an intersect of two arrays based on a field in each object in the arrays?

I’m using Rails 4.2.7. I have two arrays, (arr1 and arr2) that both contain my model objects. Is there a way to do an intersection on both arrays if an object from arr1 has a field, “myfield1,” (which is a number) that matches an object in arr2? Both arrays will have unique sets of objects. Currently I have
arr1.each_with_index do |my_object, index|
arr2.each_with_index do |my_object2, index|
if my_object.myfield1 == my_object2.myfield1
results.push(my_object)
end
end
end
but this strikes me as somewhat inefficient. I figure there’s a simpler way to get the results I need but am not versed enough in Ruby to know how to do it.
You can build an intersection of the values to find the common values, then select records that have the common values.
field_in_both = arr1.map(&:myfield1) & arr2.map(&:myfield1)
intersection = arr1.select{|obj| field_in_both.include? obj.myfield1} +
arr2.select{|obj| field_in_both.include? obj.myfield1}
I notice in your code, you're only storing records from arr1... if that's correct behaviour then you can simplify my answer
field_in_both = arr1.map(&:myfield1) & arr2.map(&:myfield1)
intersection = arr1.select{|obj| field_in_both.include? obj.myfield1}

Elegantly extracting all strings from an arbitrarily deep hash

My goal is to determine whether there is a blank in a hash like this:
{"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
The strings are nested arbitrary times. I do not want to check each of them manually. It would be nice if I can extract all the strings regardless of their depth. Is there a simple way to do that?
Here's an example of how you can extract the values. However you will still have the problem of matching the values to the keys. You can't really have both at the same time.
# Extend the Hash class
class Hash
def recursive_values
self.values.collect { |v|
v.is_a?(Hash) ? v.recursive_values : v
}.flatten
end
end
Usage:
h = {"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
h.recursive_values
=> ["", "", ""]
It will be better if you will use sth like that:
departure_time_slots = {:date => Time.new, :time_start => nil, :time_end => nil}
And when you use keys in Hash it is good practise to using Symbols for keys. (http://www.ruby-doc.org/core-1.9.3/Symbol.html)
No not possible. Because they are totally present in different scope with respect to each other.
For e.g.. keys start and end is totally unknown and masked from the departure_time_slots object in the example above.
One round abut way could be, getting all the values of the hashmap which are of type hashmap again and obtaining their keys recusively.
Fetch keys of departure_time_slots and then from the value list of that map, find all the keys from every value, if that were to be a hashmap. Other than that, I don't think there is another way.
P.S. On side note, see if u can modify your structure to an array where elements can also be arrays, and try and use flatten concept of arrays. :P

Does ActiveRecord find_all_by_X preserve order? If not, what should be used instead?

Suppose I have a non-empty array ids of Thing object ids and I want to find the corresponding objects using things = Thing.find_all_by_id(ids). My impression is that things will not necessarily have an ordering analogous to that of ids.
Is my impression correct?
If so, what can I used instead of find_all_by_id that preserves order and doesn't hit the database unnecessarily many times?
Yes
Use Array#sort
Check it out:
Thing.where(:id => ids).sort! {|a, b| ids.index(a.id) <=> ids.index(b.id)}
where(:id => ids) will generate a query using an IN(). Then the sort! method will iterate through the query results and compare the positions of the id's in the ids array.
#tybro0103's answer will work, but gets inefficient for a large N of ids. In particular, Array#index is linear in N. Hashing works better for large N, as in
by_id = Hash[Thing.where(:id => ids).map{|thing| [thing.id, thing]}]
ids.map{|i| by_id[i]}
You can even use this technique to arbitrarily sort by any not-necessarily unique attribute, as in
by_att = Thing.where(:att => atts).group_by(&:att)
atts.flat_map{|a| by_att[a]}
find_all_by_id is deprecated in rails 4, which is why I use where here, but the behavior is the same.

Ordering a hash to xml: Rails

I'm building an xml document from a hash. The xml attributes need to be in order. How can this be accomplished?
hash.to_xml
Ruby 1.8's hash aren't in insertion order. With ruby 1.9, they will be.
However rails offers an alternative to that, the class OrderedHash.
my_hash = ActiveSupport::OrderedHash.new
my_hash[:key] = 'value'
my_hash[:second_key] = 'second value'
This hash is in fact an array of that format :
[[:key, 'value'], [:second_key, 'second value']]
The entries remains in the order you inserted them.
And you can access them like with any other hash.
h = Hash[:x,123,:a,553,:d,949,:e,5321]
=> {:e=>5321, :x=>123, :a=>553, :d=>949}
h.sort { |x,y| x[0].to_s <=> y[0].to_s }
=> [[:a, 553], [:d, 949], [:e, 5321], [:x, 123]]
The usual ways of sorting a hash is by key or value. Have a look here:
hash.sort
More complex sorts can be accomplised however by utilizing the spaceship operator
What order did you want them to be in? You shouldn't expect them to be in insertion order. From the docs for Hash:
The order in which you traverse a hash
by either key or value may seem
arbitrary, and will generally not be
in the insertion order.
If you need them to be in a specific order you can determine just from the keys/values (e.g. order the attribute names alphabetically), you'll need to apply that ordering explicitly.
This piece of code I've just made for i18n-js might help you out as it convert Hash to ActiveSupport::OrderedHash if needed then sort it's key by natural order.
http://seb.box.re/2010/1/15/deep-hash-ordering-with-ruby-1-8

Resources