I want to build a hash from an array of rows from a DB. I can easily do it with the code below. I've come to Ruby from PHP and this is how I would do it. Is there a better/proper way to do this in Ruby (or Rails)?
def features_hash
features_hash = {}
product_features.each do |feature|
features_hash[feature.feature_id] = feature.value
end
features_hash
end
# {1 => 'Blue', 2 => 'Medium', 3 => 'Metal'}
You can use Hash[]:
Hash[ product_features.map{|f| [f.feature_id, f.value]} ]
Would you like this better?
product_features.map{|f| [f.feature_id, f.value]}.to_h # no available (yet?)
Then go and check out this feature request and comment on it!
Alternative solutions:
product_features.each_with_object({}){|f, h| h[f.feature_id] = f.value}
There is also group_by and index_by which could be helpful, but the values will be the features themselves, not their value.
You can use index_by for this:
product_features.index_by(&:id)
This produces the same results as hand-constructing a hash with id as the key and the records as the values.
Your code is a good way to do it. Another way is:
def features_hash
product_features.inject({}) do |features_hash, feature|
features_hash[feature.feature_id] = feature.value
features_hash
end
end
Related
I need to create the following Array:
array_time = [00:00:00, 00:00:01, ..., 23:59:59]
Is there a way to generate this type of hash with all hours of the day in ruby?
Because then I will need to create the following Hash:
hash = { "time" => { "'00:00:00'" => "23:59:59" } }
And I would like to check if the sub-Hash under key "time" uses keys in the correct format, for example:
hash["time"].each do |key|
array_time.includes key
end
Assuming that you're happy with Strings, this is a simple way to do it:
array_time = ("00".."23").flat_map do |h|
("00".."59").flat_map do |m|
("00".."59").map do |s|
"#{h}:#{m}:#{s}"
end
end
end
array_time.length
# => 86400
array_time.first(5)
# => ["00:00:00", "00:00:01", "00:00:02", "00:00:03", "00:00:04"]
array_time.last(5)
#=> ["23:59:55", "23:59:56", "23:59:57", "23:59:58", "23:59:59"]
However, if your goal is:
and I would like to check if the hash time is in the correct format, example:
hash["time"].each do |key|
array_time.include? key
end
Then that's really not the most efficient way to go about it.
First off, Hash lookups are much faster than Array#include?, so you really want to use a Hash and treat it a Set:
time_set = Hash[
("00".."23").flat_map do |h|
("00".."59").flat_map do |m|
("00".."59").map do |s|
["#{h}:#{m}:#{s}", true]
end
end
end
]
time_set
# => {"00:00:00"=>true,
# "00:00:01"=>true,
# "00:00:02"=>true,
# ...
# "23:59:58"=>true,
# "23:59:59"=>true}
And then perform your lookups like this:
hash[:time].each do |time_str|
time_set[time_str]
end
But even this is not great. Not always at least.
If you know you need to perform this check very often, with arbitrary values, and with a lot of values to check, then yes, pre-computing the lookup set once and storing it in a constant could make sense. So you'd use TIME_SET = ... instead of time_set = ....
But if this is performed sporadically, you're just much better off validating the time strings one by one. For example:
TIME_REGEX = %r{^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$}.freeze
hash[:time].each do |time_str|
TIME_REGEX === time_str
end
Assuming an array of strings is acceptable, here is one way to do it.
time_iterator = Time.at(1_500_076_800) # 2017-07-15 00:00:00 +0000
end_time = time_iterator + 1.day
array_time = []
while time_iterator < end_time
array_time << time_iterator.strftime('%T')
time_iterator += 1.second
end
Apparently in Ruby 1.9 they removed the ability to step-iterate over a time range, so a while loop seems to be preferred now.
I do think that if you're just trying to validate the format of a time-like string (HH:MM:SS) then there are much better ways to accomplish this. A simple RegEx would do it, or something similar.
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'
When I have a list of hashes, like the result of an .attributes call, what is a short way to create a line-by-line nicely readable output?
Like a shortcut for
u.attributes.each {|p| puts p[0].to_s + ": " + p[1].to_s}
I'm not sure you can make it much shorter unless you create your own method.
A minor enhancement would be:
u.attributes.each {|k,v| puts "#{k}: #{v}"}
Or you can create an extension to Hash:
class Hash
def nice_print
each {|k,v| puts "#{k}: #{v}"}
end
end
u.attributes.nice_print
AS said in my comments, I like to use y hash or puts YAML.dump(hash) that shows your hash in yaml. It can be used for other objects too.
h = {:a => 1, :b => 2, :c => 3}
# => {:a=>1, :b=>2, :c=>3}
y h
#---
#:a: 1
#:b: 2
#:c: 3
# => nil
There is also an informative answer about it.
If you are looking for an output for development purposes (in Rails log files for instance), inspect or pretty_inspect should do it :
u.attributes.inspect
or
u.attributes.pretty_inspect
But if what you are looking for is a way to print nicely in Rails console, I believe you will have to write your own method, or use a gem like awesome_print, see : Ruby on Rails: pretty print for variable.hash_set.inspect ... is there a way to pretty print .inpsect in the console?
awesome_print is the way to go
gem install awesome_print
require "ap"
ap u.attributes
I'm doing this:
#snippets = Snippet.find :all, :conditions => { :user_id => session[:user_id] }
#snippets.each do |snippet|
snippet.tags.each do |tag|
#tags.push tag
end
end
But if a snippets has the same tag two time, it'll push the object twice.
I want to do something like if #tags.in_object(tag)[...]
Would it be possible? Thanks!
I think there are 2 ways to go about it to get a faster result.
1) Add a condition to your find statement ( in MySQL DISTINCT ). This will return only unique result. DBs in general do much better jobs than regular code at getting results.
2) Instead if testing each time with include, why don't you do uniq after you populate your array.
here is example code
ar = []
data = []
#get some radom sample data
100.times do
data << ((rand*10).to_i)
end
# populate your result array
# 3 ways to do it.
# 1) you can modify your original array with
data.uniq!
# 2) you can populate another array with your unique data
# this doesn't modify your original array
ar.flatten << data.uniq
# 3) you can run a loop if you want to do some sort of additional processing
data.each do |i|
i = i.to_s + "some text" # do whatever you need here
ar << i
end
Depending on the situation you may use either.
But running include on each item in the loop is not the fastest thing IMHO
Good luck
Another way would be to simply concat the #tags and snippet.tags arrays and then strip it of duplicates.
#snippets.each do |snippet|
#tags.concat(snippet.tags)
end
#tags.uniq!
I'm assuming #tags is an Array instance.
Array#include? tests if an object is already included in an array. This uses the == operator, which in ActiveRecord tests for the same instance or another instance of the same type having the same id.
Alternatively, you may be able to use a Set instead of an Array. This will guarantee that no duplicates get added, but is unordered.
You can probably add a group to the query:
Snippet.find :all, :conditions => { :user_id => session[:user_id] }, :group => "tag.name"
Group will depend on how your tag data works, of course.
Or use uniq:
#tags << snippet.tags.uniq
What's the most elegant way to select out objects in an array that are unique with respect to one or more attributes?
These objects are stored in ActiveRecord so using AR's methods would be fine too.
Use Array#uniq with a block:
#photos = #photos.uniq { |p| p.album_id }
Add the uniq_by method to Array in your project. It works by analogy with sort_by. So uniq_by is to uniq as sort_by is to sort. Usage:
uniq_array = my_array.uniq_by {|obj| obj.id}
The implementation:
class Array
def uniq_by(&blk)
transforms = []
self.select do |el|
should_keep = !transforms.include?(t=blk[el])
transforms << t
should_keep
end
end
end
Note that it returns a new array rather than modifying your current one in place. We haven't written a uniq_by! method but it should be easy enough if you wanted to.
EDIT: Tribalvibes points out that that implementation is O(n^2). Better would be something like (untested)...
class Array
def uniq_by(&blk)
transforms = {}
select do |el|
t = blk[el]
should_keep = !transforms[t]
transforms[t] = true
should_keep
end
end
end
Do it on the database level:
YourModel.find(:all, :group => "status")
You can use this trick to select unique by several attributes elements from array:
#photos = #photos.uniq { |p| [p.album_id, p.author_id] }
I had originally suggested using the select method on Array. To wit:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gives us [2,4,6] back.
But if you want the first such object, use detect.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} gives us 4.
I'm not sure what you're going for here, though.
I like jmah's use of a Hash to enforce uniqueness. Here's a couple more ways to skin that cat:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
That's a nice 1-liner, but I suspect this might be a little faster:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Use Array#uniq with a block:
objects.uniq {|obj| obj.attribute}
Or a more concise approach:
objects.uniq(&:attribute)
The most elegant way I have found is a spin-off using Array#uniq with a block
enumerable_collection.uniq(&:property)
…it reads better too!
If I understand your question correctly, I've tackled this problem using the quasi-hacky approach of comparing the Marshaled objects to determine if any attributes vary. The inject at the end of the following code would be an example:
class Foo
attr_accessor :foo, :bar, :baz
def initialize(foo,bar,baz)
#foo = foo
#bar = bar
#baz = baz
end
end
objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)]
# find objects that are uniq with respect to attributes
objs.inject([]) do |uniqs,obj|
if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) }
uniqs << obj
end
uniqs
end
You can use a hash, which contains only one value for each key:
Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
Rails also has a #uniq_by method.
Reference: Parameterized Array#uniq (i.e., uniq_by)
I like jmah and Head's answers. But do they preserve array order? They might in later versions of ruby since there have been some hash insertion-order-preserving requirements written into the language specification, but here's a similar solution that I like to use that preserves order regardless.
h = Set.new
objs.select{|el| h.add?(el.attr)}
ActiveSupport implementation:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Now if you can sort on the attribute values this can be done:
class A
attr_accessor :val
def initialize(v); self.val = v; end
end
objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)}
objs.sort_by{|a| a.val}.inject([]) do |uniqs, a|
uniqs << a if uniqs.empty? || a.val != uniqs.last.val
uniqs
end
That's for a 1-attribute unique, but the same thing can be done w/ lexicographical sort ...
If you are not married with arrays, we can also try eliminating duplicates through sets
set = Set.new
set << obj1
set << obj2
set.inspect
Note that in case of custom objects, we need to override eql? and hash methods