Dynamically creating a multi-dimensional hash in Ruby
@John's Enumerable#group_by
suggestion is one good way to solve your needs. Another would be to create an auto-vivifying Hash (like you appear to have in PHP) like so:
hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
hash[:a][:b][:c] = 42
p hash
#=> {:a=>{:b=>{:c=>42}}}
Note that this sort of auto-vivification can be 'dangerous' if you access keys that don't exist, as it creates them for you:
p hash["does this exist?"]
#=> {}
p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}
You can still use the vivifying default_proc
without hitting this danger if you use key?
to test for the key first:
val = hash["OH NOES"] if hash.key?("OH NOES")
#=> nil
p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}
FWIW, the error you are getting says, "Hey, you put []
after something that evaluated to nil
, and nil
doesn't have a []
method." Specifically, your code...
sorted_pois[point.file_type.to_sym]
evaluated to nil
(because the hash did not yet have a value for this key) and then you attempted to ask for
nil[point.file.to_sym]
How to make dynamic multi-dimensional array in ruby?
You are getting the error because a FixNum
(that is, a number) is pushed on the array, in the line that reads @years.push(year)
.
Your approach of using Arrays to start with is a bit flawed; an array is perfect to hold an ordered list of items. In your case, you have a mapping from keys to values, which is perfect for a Hash.
In the first level, the keys are years, the values are hashes. The second level's hashes contain keys of months, and values of arrays of entries.
In this case, a typical output of your code would look something like (based on your example):
{ 2009 => { 8 => [Entry1, Entry2], 9 => [Entry3] }, 2007 => { 10 => [Entry5] }}
Notice that, however, the order of years and months is not guaranteed to be in any particular order. The solution is normally to order the keys whenever you want to access them. Now, a code that would generate such an output (based on your layout of code, although can be made much rubier):
@years = {}
@entries.each do |entry|
timeobj = Time.parse(entry.created_at.to_s)
year = timeobj.strftime("%Y").to_i
month = timeobj.strftime("%m").to_i
@years[year] ||= {} # Create a sub-hash unless it already exists
@years[year][month] ||= []
@years[year][month] << entry
end
creating dynamic hash with key and value in ruby on rails
You are re-assigning test
with a new hash on each iteration. You should add to it, so instead of
test ={i => s1.power}
you should do:
test[i] = s1.power
This sets the value of key i
to s1.power
If you want to keep an array of all the values for a given key, I would suggest the following (more ruby-ish) solution:
hour_idx = array.find_index { |item| s.duration.start.hour == item.hour }
values = case s.type.to_s
when 'M'
s.sgs.map(&:_power)
when 'L'
puts "to be done"
else
puts "Not Found"
end
test = { hour_idx => values }
What I'm doing here is:
- Find the
hour_idx
which is relevant to the currents
(I assume there is only one such item) - Create an array of all the relevant values according to
s.type
(if it is'M'
an array of all the_power
ofs.sgs
, for'L'
whatever map you need, andnil
otherwise) - Create the target hash using the values set in #1 and #2...
Ruby: Test multidimensional hash
You don't get nil
or a String
back in case of a failed lookup - instead you get an empty Hash
.
Check if commandid.is_a?(Hash)
and commandid.empty?
to know if the lookup failed or not.
Dynamically create Hash and Arrays
Maybe it's not exactly what you're asking for, but at least it's readable:
data = [1,2,3,4]
h = data.each_with_object({}) { |e, m| m[e] = func1(e) }
a = data.map &method(:func2)
b = data.map &method(:func3)
Though, as you said, your example is a simplification of a real task, so it might not work for you. In that case I'd recommend to stay with your original solution.
How to dynamically add a hash to another hash in Ruby on Rails?
First get the parents and then get children using .map
:
#Data
comments = [{comment_id: 1, parent_id: nil, text: "parent 1 "}, {comment_id: 2, parent_id: nil, text: "parent 2 "}, {comment_id: 3, parent_id: 1, text: "reply for comment 1"}, {comment_id: 4, parent_id: 2, text: "reply for comment 2"}]
# get the parents first
parents = comments.map{ |comment| comment if comment[:parent_id] == nil }.compact
# children refers to each comment as parent with replies as children
children = parents.map {|parent| parent.merge({children: comments.map {|children| children if children[:parent_id] == parent[:comment_id]}.compact })}
Result:
[{:comment_id=>1, :parent_id=>nil, :text=>"parent 1 ", :children=>[{:comment_id=>3, :parent_id=>1, :text=>"reply for comment 1"}]}, {:comment_id=>2, :parent_id=>nil, :text=>"parent 2 ", :children=>[{:comment_id=>4, :parent_id=>2, :text=>"reply for comment 2"}]}]
Hash of Hashes in Ruby
Use a vivified hash as shown in "Dynamically creating a multi-dimensional hash in Ruby"
Or use group_by
.
Ruby: Unexpected result from Hash.keys in multi-dimensional hash
You just set the default value that will be returned if no value is found. This doesn't change the fact that there is no value assigned to h["foo"]
. {"bar"=>6}
will be the value for any key which is not found.
h = {}
h.default = {} # => {}
h["foo"]["bar"] = 6 # => 6
h["foo"] # => {"bar"=>6}
h["baz"] # => {"bar"=>6}
If you wanted a hash which returns and sets values of missing keys to empty hashes, you have to do:
h = Hash.new { |hash, key| hash[key] = {} }
h["foo"]["bar"] = 6 # => 6
h # => {:foo=>{:bar=>6}}
One liner nested hash creation in Ruby? (I come from Perl)
Unfortunately, there is no simple, practical way. A Ruby equivalent would be an ugly, ugly beast like:
((((@date[month] ||= {})[day] ||= {})[hours] ||= {})[min] ||= {})[sec] = 1
There is a way to assign default values for missing keys in hashes, though:
@date = Hash.new { |hash, key| hash[key] = {} }
# @date[:month] is set to a new, empty hash because the key is missing.
@date[:month][:day] = 1
Unfortunately this does not work recursively.
...unless you create it yourself; hooray for Ruby!
class Hash
def self.recursive
new { |hash, key| hash[key] = recursive }
end
end
@date = Hash.recursive
@date[month][day][hours][min][sec] = 1
# @date now equals {month=>{day=>{hours=>{min=>{sec=>1}}}}}
Keep in mind, though, that all unset values are now {}
rather than nil
.
Divide data into multiple hashes on the basis of timestamp
Order notifications by timestamp, add a column to select what wil have date only and then use group_by
in Ruby. That should be the easiest way to have it.
For selecting additional column to work you should add attr_accessor
for it.
http://www.ruby-doc.org/core-2.0/Enumerable.html#method-i-group_by
Related Topics
Nested Object Creation with JSON in Rails
Ruby/Rails: Convert Int to Time or Get Time from Integer
How to Call an Older Version of a Gem from the Commandline
How to Find If a String Starts with Another String in Ruby
Execute a Rake Task from Within Migration
How to List All Objects Created from a Class in Ruby
Hot Deploy on Heroku with No Downtime
How to Cache a Calculated Column in Rails
Is There an Equivalent to 'Array::Sample' for Hashes
Heroku Rails 4 Could Not Connect to Server: Connection Refused
Dynamic Rails Routes Based on Database Models
Rails Fixtures Not Loading with Rspec
Consistent String#Hash Based Only on the String's Content
I Want to Override Authenticate_User and Current_User Method of Devise Gem