Call a Hash value with a variable - Ruby - ruby

How can I call a hash value with a variable?
I have a hash like this:data = {"5/3/2013 13:31:13"=>{:open=>65, :closed=>835}}
datasequences.each do |seq_title|
sequence = Hash.new(0)
sequence[:title] = seq_title
sequence_data = Array.new(0)
data.each do |key, value|
puts value[#{seq_title.to_sym}]
# More code...
end
end
The per the Hash (data), the value of seq_title will be open and then closed.
For example if I change the code to read
datasequences.each do |seq_title|
sequence = Hash.new(0)
sequence[:title] = seq_title
sequence_data = Array.new(0)
data.each do |key, value|
puts value[:open]
# More code...
end
end
In the above code Ruby outputs 65, but I want value[var] (not hardcoded) so it can output 65 and through the next iteration 835.

You just need to specify the key with a variable:
% irb
irb> data = { "5/3/2013 13:31:13"=>{:open=>65, :closed=>835}, "5/4/2013 14:41:14"=>{:open=>56, :closed=>538} }
# => {"5/3/2013 13:31:13"=>{:open=>65, :closed=>835}, "5/4/2013 14:41:14"=>{:open=>56, :closed=>538}}
irb> datasequences = [ :open, :closed ]
# => [:open, :closed]
irb> datasequences.each do |seq_title|
irb| puts "seq_title is #{seq_title.inspect}"
irb| data.each do |key, value|
irb| puts "\t#{value[seq_title]} at #{key}"
irb| end
irb| end
seq_title is :open
65 at 5/3/2013 13:31:13
56 at 5/4/2013 14:41:14
seq_title is :closed
835 at 5/3/2013 13:31:13
538 at 5/4/2013 14:41:14
# => [:open, :closed]

Since the value is itself a hash, you print the keys and values like this:
data.each do |key, value|
value.each do |k,v|
puts "#{k}: #{v}"
end
# More code...
end

You need to use Hash#values_at, like below:
data = {"5/1/2013 10:42:40" => {"open" => 10,"closed" => 345},"5/2/2013 10:42:40" => {"open" => -1,"closed" => 700}}
datasequences = [ :open, :closed ]
data.each do |k,v|
p v.values_at(*datasequences.map(&:to_s))
end
output:
[10, 345]
[-1, 700]

Related

How can I parse a string into a hash?

I am trying to parse a string into a hash.
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
The result should look similar to this:
pairs = {
'Open' = 1,
'Published' => true
'Body' => {
'Message' => [3455, 2524, 2544, 2452],
'Error' => [2455],
'Currency' => 'EUR',
}
}
Maybe someone can help on how I can make it. The only way I can think as for now is regexp.
something like this with regexp:
require 'pp'
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
pairs = {}
pairs['Body'] = {}
values = []
str.scan(/Body\W+(.+)/).flatten.each do |line|
key = line[/\A\w+/]
value = line[/\w+\z/]
if line[/\A\w+\[\d+\]/] || key == 'Error'
values = [] unless pairs['Body'][key]
values << value
value = values
end
pairs['Body'][key] = value
end
str.scan(/\[0\]\.(?!Body.).*/).each do |line|
key = line[/(?!\A)\.(\w+)/, 1]
value = line[/\w+\z/]
if line[/\A\w+\[\d+\]/]
values = [] unless pairs[key]
values << value
value = values
end
pairs[key] = value
end
PP.pp pairs
-
{"Body"=>
{"Message"=>["3455", "2524", "2544", "2452"],
"Error"=>["2455"],
"Currency"=>"EUR"},
"Open"=>"1",
"Published"=>"true"}
Here it is. This code should work with any structure.
def parse(path, value, hash)
key, rest = path.split('.', 2)
if rest.nil?
hash[key] = value
else
hash[key] ||= {}
parse(rest, value, hash[key])
end
end
def conv_to_array(hash)
if hash.is_a?(Hash)
hash.each do |key, value|
hash[key] = if value.is_a?(Hash) && value.keys.all? { |k| k !~ /\D/ }
arr = []
value.each do |k, v|
arr[k.to_i] = conv_to_array(v)
end
arr
else
conv_to_array(value)
end
end
hash
else
if hash !~ /\D/
hash.to_i
elsif hash == 'true'
true
elsif hash == 'false'
false
else
hash
end
end
end
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
str = str.tr('[', '.').tr(']', '')
hash = {}
str.split(' ').each do |chunk|
path, value = chunk.split('=')
parse(path.strip, value.strip, hash)
end
hash = conv_to_array(hash)
hash['Notifications'][0]
# => {"Open"=>1, "Body"=>{"Message"=>[3455, 2524, 2544, 2452], "Error"=>[2455], "Currency"=>"EUR"}, "Published"=>true}

Ruby: initialize a Ruby Class with a nested Hash and some pre-defined default values

Here is my problem. I like Andrea Pavoni's way of allowing a nested hash to be used to initialize a class.
require 'ostruct'
class DeepStruct < OpenStruct
def initialize(hash=nil)
#table = {}
#hash_table = {}
if hash
hash.each do |k,v|
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def to_h
#hash_table
end
end
But I can't find a way to include a hash (in the class) with specific default values, so that the behavior would be as follows:
Original behavior without default (with above code):
input_hash = {a: {b: 1}}
new_object = DeepStruct.new hash
new_object.a # => #<DeepStruct b=1>
new_object.a.b # => 1
new_object.a.to_h # => {b: 1}
With the following default_h defined inside the class:
default_h = {a: {dc: 2}, dd: {de: 4}}
input_hash and default_h should merge as follows
(actually using deep_merge for nested hash)
{:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}}
The behavior with default hash should be:
new_object = DeepStruct.new hash
new_object.a.b # => 1
new_object.a.dc # => 2
new_object.a.to_h # => {:dc=>2, :b=>1}
I can't find a way to implement this behavior inside the class. I would really appreciate any help in this matter.
Edit: Now trying to use David's code in a class:
class CompMedia
require 'ostruct'
attr_accessor :merged_h
def initialize(hash)
defaults = {a: {dc: 2}, dd: {de: 4}}
#merged_h = {}
deep_update(merged_h, defaults)
deep_update(merged_h, hash)
#merged_h
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def deep_open_struct(hash)
result = OpenStruct.new
hash.each do |key, value|
if value.is_a?(Hash)
result[key] = deep_open_struct(value)
else
result[key] = value
end
end
result
end
end # class CompMedia
input_hash = {a: {b: 1}}
cm = CompMedia.new(input_hash)
object = cm.deep_open_struct(cm.merged_h)
p object.marshal_dump # {:a=>#<OpenStruct dc=2, b=1>, :dd=>#<OpenStruct de=4>}
p object.a # <OpenStruct dc=2, b=1>
p object.a.marshal_dump # {:dc=>2, :b=>1}
p object.a.b # 1
p object.a.dc # 2
p object.dd # <OpenStruct de=4>
Obviously, I haven't found a way to retrieve in a simple fashion the nested hash elements from the openstruct object.
My objective is to create a class that would be initialized with a default (nested) hash contained in the class, and a (nested) input hash. In addition, I want to be able to add methods that would process the hash inside the class. I am not there yet.
On the other hand, I could just use the merged hash and this would work albeit with slightly more cumbersome notations:
class CompMedia
attr_accessor :merged_h
def initialize(hash)
defaults = {a: {dc: 2}, dd: {de: 4}}
#merged_h = {}
deep_update(merged_h, defaults)
deep_update(merged_h, hash)
#merged_h
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def multiply_by(k)
merged_h[:a][:dc] * k
end
end
input_hash = {a: {b: 1}}
cm = CompMedia.new(input_hash)
p cm.merged_h # {:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}}
p cm.merged_h[:a] # {:dc=>2, :b=>1}
p cm.merged_h[:a][:dc] # 2
p cm.merged_h[:dd] # {:de=>4}
p cm.multiply_by(10) # 20
I will consider the last version as my solution unless someone can make the code with OpenStruct work, which I would prefer.
Here is some code that does what you want, except I threw away the idea of subclassing OpenStruct because I wasn't sure if it was a good idea. Also, I implemented deep_merge myself because it was pretty easy to do, but you could try using the version from ActiveSupport if you wanted.
require 'ostruct'
# Merges two hashes that could have hashes inside them. Default
# values/procs of the input hashes are ignored. The output hash will
# not contain any references to any of the input hashes, so you don't
# have to worry that mutating the output will affect the inputs.
def deep_merge(h1, h2)
result = {}
deep_update(result, h1)
deep_update(result, h2)
result
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def deep_open_struct(hash)
result = OpenStruct.new
hash.each do |key, value|
if value.is_a?(Hash)
result[key] = deep_open_struct(value)
else
result[key] = value
end
end
result
end
input_hash = {a: {b: 1}}
defaults = {a: {dc: 2}, dd: {de: 4}}
object = deep_open_struct(deep_merge(defaults, input_hash))
p object.a.b
p object.a.dc
p object.a.to_h

Check to see if array contains string or int and complete if/else statement

Please help to explain what is needed in my code to decipher if the array contains an integer, if it does I need to add 1 to it and if doesn't if will just display the string or symbol.
I have left #notes where my brain stopped working
# possible arrays
# array = [1, "two", :three]
# array = [1, 2, 3]
class Array
def new_map
result = []
self.each do |item|
yield(item)
if #check to see if item is an integer
result << item + 1
else
# add string to result array
end
end
result
end
end
Here is the Rspec test:
describe "Array" do
describe "new_map" do
it "should not call map" do
a = [1, 2, 3]
a.stub(:map) { '' }
a.new_map { |i| i + 1 }.should eq([2, 3, 4])
end
it "should map any object" do
a = [1, "two", :three]
a.new_map { |i| i.class }.should eq([Fixnum, String, Symbol])
end
end
end
if item.is_a? Integer
result << item + 1
class Array
def new_map
result = []
self.each do |item|
yield(item)
if item.class == Integer # or if item.is_a? Integer
result << item + 1
else
# add string to result array
end
end
result
end
end
example:
=> 1.is_a? Integer
=> true
=> "1".is_a? Integer
=> false
=> 1_000_000.is_a? Integer
=> true
=> 1_000_000.class
=> Fixnum
=> 1_000_000.is_a? Integer
=> true
Try this:
class Array
def new_map
map do |item|
yield(item)
end
end
end
Actually you first spec does not make sense. You yield i + 1 to the block. This must fail if ì is not a Fixnum. You must not check if something is an Integer in your method, but in the block. This should work:
describe "Array" do
describe "new_map" do
it "should not call map" do
a = [1, 2, 3]
a.new_map { |i| (i.is_a?(Integer) ? i + 1 : i) }.should eq([2, 3, 4])
end
it "should map any object" do
a = [1, "two", :three]
a.new_map { |i| i.class }.should eq([Fixnum, String, Symbol])
end
end
end
class Array
def new_map
result = []
self.each do |item|
result << yield(item)
end
result
end
end
And both your tests pass, don't check object's class unnecessarily, trust duck typing.

How to refactor this Ruby sanitize hash method to make it more idiomatic?

This method takes a hash and returns a new hash without sensitive information. It does not modify the hash passed in.
Is there a more Ruby-like, idiomatic way of doing it?
def sanitize hash
new_hash = hash.dup
protected_keys = [ :password, 'password', :confirmation, 'confirmation' ]
new_hash.each do |k,v|
if protected_keys.include?( k ) && ! v.blank?
new_hash[ k ] = 'xxxxxxxxx'
end
end
new_hash
end
Working in Ruby 1.9.3, Sinatra (not Rails) and not using Active Record.
Perhaps something like:
class Hash
def sanitize(*keys)
new_hash = self.dup
new_hash.each do |k,v|
if keys.include?(k) && ! v.empty?
new_hash[k] = 'xxxxxxxxxx'
end
end
end
def sanitize!(*keys)
self.each do |k,v|
if keys.include?(k) && ! v.empty?
self[k] = 'xxxxxxxxxx'
end
end
end
end
You can then call
hash = {password: "test", name: "something"}
sanitized_hash = hash.sanitize(:password, 'password', :confirmation, 'confirmation')
And then sanitize! will modify the Hash in place without duping per Ruby standards.
It is inefficient to iterate over the protected keys for each key in the hash as in your solution. Rather, just iterate over the protected keys.
It is inefficient to generate the array of protected keys each time the method is called. Define that array outside of the method.
The following is better in these respects:
ProtectedKeys = %w[password confirmation]
def sanitize hash
new_hash = hash.dup
ProtectedKeys.each do |k| [k, k.to_sym].each do |k|
new_hash[k] = "xxxxxxxxx" if new_hash.key?(k) and new_hash[k].present?
end end
new_hash
end
And another one:
def sanitize(params)
protected_keys = %(password confirmation)
replacement = 'xxxxxx'
new_params = params.dup
new_params.each_key {|key| new_params[key] = replacement if protected_keys.include?(key.to_s)}
end
test_hash = {
name: 'Me',
password: 'secret',
address: 'Here',
confirmation: 'secret'
}
puts sanitize(test_hash)

Ruby hash of hash of hash

How can I have a hash of hash of hash?
My test returns
undefined method `[]' for nil:NilClass (NoMethodError)
Any tips?
found = Hash.new()
x = 1;
while x < 4 do
found[x] = Hash.new()
y = 1
while y < 4 do
found[x][y] = Hash.new()
found[x][y]['name1'] = 'abc1'
found[x][y]['name2'] = 'abc2'
found[x][y]['name3'] = 'abc3'
y += 1
end
x += 1
end
found.each do |k, v, y|
puts "k : #{k}"
puts " : #{v[y['name1']]}"
puts " : #{v[y['name2']]}"
puts " : #{v[y['name3']]}"
puts
end
I think you want something like this:
First of all create the data structure. You want nested hashes so you need to define default values for each hash key.
found = Hash.new do |hash,key|
hash[key] = Hash.new do |hash,key|
hash[key] = Hash.new
end
end
Run the search
(1..3).each do |x|
(1..3).each do |y|
found[x][y]['name1'] = 'abc1'
found[x][y]['name2'] = 'abc1'
found[x][y]['name3'] = 'abc1'
end
end
Then display the results
found.each do |x, y_hash|
y_hash.each do |y, name_hash|
name_hash.each do |name, value|
puts "#{x} => #{y} => #{name} => #{value}"
end
end
end
The way you build the hash seems to be functional. What probably causes the error is this loop:
found.each do |k, v, y|
Hash#each yields key/value pairs, so y will be assigned nil, thus causing the error two lines below. What you probably meant is a nested loop like
found.each do |x, h1|
h1.each do |y, h2|
puts h2['name1']
end
end
You should also be aware that you can write these kinds of counting loops more concisely in Ruby:
found = Hash.new { |h,k| h[k] = {} }
1.upto(3) do |x|
1.upto(3) do |y|
found[x][y] = {
'name1' => 'abc1',
'name2' => 'abc2',
'name3' => 'abc3',
}
end
end

Resources