I'm trying to create different kind of structure from the array I currently have in my code.
I've got an array of objects (active directory objects) so lets say i got a from db :
a = [o1,o2,o3,o4,o5]
My object has property source_id and name which are the relevant properties.
I want to create structure like this (I want hash) from the data I have in my array :
objects = Hash.new { |hash, key| hash[key] = [] }
And this would be one example of how to put the data inside new structure:
a.each do |ob|
objects[ob.source_id] << {
:new => '',
:name => {:unformated => ob.name, :formatted => ob.format(:name)}
}
end
I'm trying to replicate the same structure and it's not working out in my case :
a.group_by(&:source_id).map do |k,v|
{
k=> {
{
:new => '',
:name => {:unformated => v.name, :formatted => ob.format(:name)}
}
}
}
end.reduce(:merge)
This is the error I get :
! #<NoMethodError: undefined method `name' for #<Array:0xae542b4>>
With group_by your values in the a.group_by(&:source_id).map are going to be arrays of elements sharing the same source_id and not individual elements.
The following code may do what you wish:
a.group_by(&:source_id).map do |k,v|
{
k => v.map do |e|
{
:new => '',
:name => {:unformated => e.name, :formatted => e.format(:name)}
}
end
}
end.reduce(:merge)
Heres a one liner to do the same
new_hash = a.each_with_object({}) { |o, hash| hash[o.source_id] = {:new => '', :unformatted => o.name, :formatted => o.format(:name)} }
This does require ruby 1.9.3 though
Related
I am unable to come up with a nice way to access a multidimensional Hash with supplied key names in a splat operator - any suggestions?
Example:
I am having a Hash like
{
'key' => 'value',
'some' => {
'other' => {
'key' => 'othervalue'
}
}
}
and a function definition def foo(*args)
I want to return foo('key') value and foo('some','other','key') othervalue. All I can come up with are rather long and ugly for loops with a lot of nil? checks, and am somehow sure I am missing a more ruby-ish way to do this nice and short. Any hints are appreciated.
Update
Using the reply by Patrick below, I came up with
def foo(hash, *args)
keys.reduce(hash, :fetch)
end
which works as I expect it to. Thanks!
In some other languages this is known as get_in, for example in Clojure and Elixir. Here's a functional-ish implementation in Ruby:
class Hash
def get_in(*keys)
keys.reduce(self, :fetch)
end
end
Usage:
h = {
'key' => 'value',
'some' => {
'other' => {
'key' => 'othervalue'
}
}
}
h.get_in 'some'
#=> {
# "other" => {
# "key" => "othervalue"
# }
# }
h.get_in 'some', 'other'
#=> {
# "key" => "othervalue"
# }
h.get_in 'some', 'other', 'key'
#=> "othervalue"
Thank you for the help in advance!
I'd like to add or push a value into my array which is currently within a person hash and task hash.
Look below at the add task section task.merge!
task = {
"person1" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
"person2" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
"person3" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
}
puts task["person1"][:high][0]
# create task
#create new person hash within task hash
#already have :high, :mid, & :low hash
# add task
#add a task to the array within either high,mid, or low
task.merge!("person1" => {:high => "#{new_task}")
# remove task
#delete method
task.merge! "person1" => {:high => "#{new_task}"}
You simply had an error there. I removed the parenthesis to make it maybe a little more obvious.
I am trying to create a hash that has a data structure like so:
hash = {
:a => { :a1 => "x", :a2: => "x", :a3 => "x" },
:b => { :b1 => "x", :b2: => "x", :b3 => "x" },
}
Inside of a class function. I am rather new to OO, so maybe I'm not understanding the variable scoping correctly.
Here is my code:
class Foo
# Control class args
attr_accessor :site, :dir
# Initiate our class variables
def initialize(site,dir)
#site = site
#dir = dir
##records = {}
#records = Hash.new { |h, k| h[k] = Hash.new }
end
def grab_from_it
line = %x[tail -1 #{#dir}/#{#site}/log].split(" ")
time = line[0, 5].join(" ")
rc = line[6]
host = line[8]
ip = line[10]
file = line[12]
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
end
end
Main body:
foo = Foo.new(site,dir)
foo.grab_from_it
pp foo
sleep(10)
foo.grab_from_it
pp foo
It works and successfully creates a hash with my desired structure, but when I run again, it overwrites the existing hash. I want it to keep adding to it, so I can create a "running tab".
Replace the following line
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
with
#records["#{file}"] = { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" }
Every time you call #records = {} the instance variable points to a new hash. Thus the initialization code in initialize has no effect. Rather than replace the initialized hash with a new one, you should add new entry to the existing hash, using the []= instance method of Hash.
BTW, you can use variable to refer to the string rather than creating a new one using string interpolation "#{variable}".
#records[file] = { :time => time, :rc => rc, :host => host, :ip => ip }
If you want the UPDATE behavior for both the first and the second layers of the hash, you can take a look at the Hash#update method.
#records[file].update({ :time => time, :rc => rc, :host => host, :ip => ip })
This question already has answers here:
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
Given a hash such as:
AppConfig = {
'service' => {
'key' => 'abcdefg',
'secret' => 'secret_abcdefg'
},
'other' => {
'service' => {
'key' => 'cred_abcdefg',
'secret' => 'cred_secret_abcdefg'
}
}
}
I need a function to return service/key in some cases and other/service/key in other cases. A straightforward way is to pass in the hash and an array of keys, like so:
def val_for(hash, array_of_key_names)
h = hash
array_of_key_names.each { |k| h = h[k] }
h
end
So that this call results in 'cred_secret_abcdefg':
val_for(AppConfig, %w[other service secret])
It seems like there should be a better way than what I've written in val_for().
def val_for(hash, keys)
keys.reduce(hash) { |h, key| h[key] }
end
This will raise an exception if some intermediate key is not found. Note also that this is completely equivalent to keys.reduce(hash, :[]), but this may very well confuse some readers, I'd use the block.
%w[other service secret].inject(AppConfig, &:fetch)
appConfig = {
'service' => {
'key' => 'abcdefg',
'secret' => 'secret_abcdefg'
},
'other' => {
'service' => {
'key' => 'cred_abcdefg',
'secret' => 'cred_secret_abcdefg'
}
}
}
def val_for(hash, array_of_key_names)
eval "hash#{array_of_key_names.map {|key| "[\"#{key}\"]"}.join}"
end
val_for(appConfig, %w[other service secret]) # => "cred_secret_abcdefg"
Ruby 2.3.0 introduced a new method called dig on both Hash and Array that solves this problem entirely.
AppConfig.dig('other', 'service', 'secret')
It returns nil if the key is missing at any level.
I'm playing around with nested hashes and I'm trying to figure out how to fetch multiple keys when my hash is a nested one:
imahash = { :id => { :name => "Alma", :email => "alma#mail.com" },
:stats => { :gender => "Female" },
:location => { :city => "Freeport", :state => "Maine" }
}
I know how to retrieve the nested value, and typing in the hash name will dump all the keys and values. But what I want to do is to fetch specific keys, such as :name and :gender only. Or :name and :city only.
Is this possible? Because from what I've found, it seems that you can only retrieve hash values for one key at a time or for all the keys at once.
My desired output would be something like:
=> { :id => { :name => "Alma" }, :location => { :city => "Freeport" } }
I presume you want to grab the values out in a tuple? You can make an array that contains whatever collection of values you want.
Try the following for name and city:
[imahash[:id][:name], imahash[:location][:city]]
=> ["Alma", "Freeport"]
Not exactly sure what you're asking here, but it seems like you're wanting to create a new hash from the bigger one.
To fetch specific keys like :name and :gender only
name_and_gender_hash = {
:name => imahash[:id][:name],
:gender => imahash[:stats][:gender]
}
would result in
{:name => "Alma", :gender => "female"}