Dynamically Creating a Multi-Dimensional Hash in Ruby

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:

  1. Find the hour_idx which is relevant to the current s (I assume there is only one such item)
  2. Create an array of all the relevant values according to s.type (if it is 'M' an array of all the _power of s.sgs, for 'L' whatever map you need, and nil otherwise)
  3. 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



Leave a reply



Submit