I have a hash
original_hash = {"10"=>3, "15"=>2, "20"=>1}
I want to make a new hash, call it results_hash, where the keys of original_hash will be appended '$' sign and the new values to the keys will be (key * value) of the original_hash. The generated results_hash should be:
results_hash = {"$10"=>30, "$15"=>30, "$20"=>20}
How can I do that?
resulted_hash = {}
original_hash.each do |key, val|
resulted_hash["$" + k] = v*k.to_i
end
original_hash = {"10"=>3, "15"=>2, "20"=>1}
results_hash = Hash[original_hash.map { |k,v| ['$'+k,k.to_i*v] }]
p results_hash # => {"$10"=>30, "$15"=>30, "$20"=>20}
original_hash = {"10"=>3, "15"=>2, "20"=>1}
results_hash = original_hash.each_with_object({}) { |(k,v),h| h['$'+k]=k.to_i*v }
p results_hash # => {"$10"=>30, "$15"=>30, "$20"=>20}
Related
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}
h = {
data: {
user: {
value: "John Doe"
}
}
}
To assign value to the nested hash, we can use
h[:data][:user][:value] = "Bob"
However if any part in the middle is missing, it will cause error.
Something like
h.dig(:data, :user, :value) = "Bob"
won't work, since there's no Hash#dig= available yet.
To safely assign value, we can do
h.dig(:data, :user)&.[]=(:value, "Bob") # or equivalently
h.dig(:data, :user)&.store(:value, "Bob")
But is there better way to do that?
It's not without its caveats (and doesn't work if you're receiving the hash from elsewhere), but a common solution is this:
hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
hash[:data][:user][:value] = "Bob"
p hash
# => { :data => { :user => { :value => "Bob" } } }
And building on #rellampec's answer, ones that does not throw errors:
def dig_set(obj, keys, value)
key = keys.first
if keys.length == 1
obj[key] = value
else
obj[key] = {} unless obj[key]
dig_set(obj[key], keys.slice(1..-1), value)
end
end
obj = {d: 'hey'}
dig_set(obj, [:a, :b, :c], 'val')
obj #=> {d: 'hey', a: {b: {c: 'val'}}}
interesting one:
def dig_set(obj, keys, value)
if keys.length == 1
obj[keys.first] = value
else
dig_set(obj[keys.first], keys.slice(1..-1), value)
end
end
will raise an exception anyways if there's no [] or []= methods.
I found a simple solution to set the value of a nested hash, even if a parent key is missing, even if the hash already exists. Given:
x = { gojira: { guitar: { joe: 'charvel' } } }
Suppose you wanted to include mario's drum to result in:
x = { gojira: { guitar: { joe: 'charvel' }, drum: { mario: 'tama' } } }
I ended up monkey-patching Hash:
class Hash
# ensures nested hash from keys, and sets final key to value
# keys: Array of Symbol|String
# value: any
def nested_set(keys, value)
raise "DEBUG: nested_set keys must be an Array" unless keys.is_a?(Array)
final_key = keys.pop
return unless valid_key?(final_key)
position = self
for key in keys
return unless valid_key?(key)
position[key] = {} unless position[key].is_a?(Hash)
position = position[key]
end
position[final_key] = value
end
private
# returns true if key is valid
def valid_key?(key)
return true if key.is_a?(Symbol) || key.is_a?(String)
raise "DEBUG: nested_set invalid key: #{key} (#{key.class})"
end
end
usage:
x.nested_set([:instrument, :drum, :mario], 'tama')
usage for your example:
h.nested_set([:data, :user, :value], 'Bob')
any caveats i missed? any better way to write the code without sacrificing readability?
Searching for an answer to a similar question I developmentally stumbled upon an interface similar to #niels-kristian's answer, but wanted to also support a namespace definition parameter, like an xpath.
def deep_merge(memo, source)
# From: http://www.ruby-forum.com/topic/142809
# Author: Stefan Rusterholz
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
memo.merge!(source, &merger)
end
# Like Hash#dig, but for setting a value at an xpath
def bury(memo, xpath, value, delimiter=%r{\.})
xpath = xpath.split(delimiter) if xpath.respond_to?(:split)
xpath.map!{|x|x.to_s.to_sym}.push(value)
deep_merge(memo, xpath.reverse.inject { |memo, field| {field.to_sym => memo} })
end
Nested hashes are sort of like xpaths, and the opposite of dig is bury.
irb(main):014:0> memo = {:test=>"value"}
=> {:test=>"value"}
irb(main):015:0> bury(memo, 'test.this.long.path', 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}}}}
irb(main):016:0> bury(memo, [:test, 'this', 2, 4.0], 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):017:0> bury(memo, 'test.this.long.path.even.longer', 'value')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):018:0> bury(memo, 'test.this.long.other.even.longer', 'other')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}, :other=>{:even=>{:longer=>"other"}}}, :"2"=>{:"4.0"=>"value"}}}}
A more ruby-helper-like version of #niels-kristian answer
You can use it like:
a = {}
a.bury!([:a, :b], "foo")
a # => {:a => { :b => "foo" }}
class Hash
def bury!(keys, value)
key = keys.first
if keys.length == 1
self[key] = value
else
self[key] = {} unless self[key]
self[key].bury!(keys.slice(1..-1), value)
end
self
end
end
a beginner question here, please help me out
my_array = ["city1:state1","city2:state2","city3:state1","city4:state3","city5:state1"]
from this array how can i make a hash like this?
{
state1: [city1,city3,city5],
state2: [city2],
state3: [city4]
}
i am trying this way
my_hash = { }
my_array.each do |cs|
temp = cs.split(":")
if my_hash.keys.include? temp[1]
my_hash[temp[1]] = temp[0]
else
my_hash[temp[1]] = temp[0]
end
end
but i am not getting how to match keys of my hash and append to keys.
A little modification can work:
my_hash = { }
my_array.each do |cs|
temp = cs.split(":")
if my_hash.keys.include? temp[1].to_sym
my_hash[temp[1].to_sym] << temp[0]
else
my_hash[temp[1].to_sym] = [temp[0]]
end
end
Result is {:state1=>["city1", "city3", "city5"], :state2=>["city2"], :state3=>["city4"]}. I'm assuming this is what you mean (the keys are symbols, and the values are arrays of strings).
You can use Hash defaults to achieve an alternative solution:
my_array = ["city1:state1","city2:state2","city3:state1","city4:state3","city5:state1"]
hash = Hash.new do |hash, key|
hash[key] = []
end
my_array.each_with_object(hash) do |string, hash|
city, state = string.split(":")
hash[state.to_sym] << city
end
# => {:state1=>["city1", "city3", "city5"], :state2=>["city2"], :state3=>["city4"]}
Try this(considering that you mistyped state3:[city3] instead of state3:[city4] in your question):
my_array = ["city1:state1","city2:state2","city3:state1","city4:state3","city5:state1"]
my_hash = { }
my_array.each do |cs|
value, key = cs.split(":")
key = key.to_sym
if my_hash[key].present?
my_hash[key] << value
else
my_hash[key] = [value]
end
end
my_hash #=> {:state1=>["city1", "city3", "city5"], :state2=>["city2"], :state3=>["city4"]}
Or, one-liner:
my_hash = my_array.inject({}){|h, cs| value, key = cs.split(":"); key = key.to_sym; h[key].present? ? (h[key] << value) : h[key] = [value]; h }
my_hash #=> {:state1=>["city1", "city3", "city5"], :state2=>["city2"], :state3=>["city4"]}
or even better(based on jesper's idea of Hash):
my_array.inject(Hash.new{|h,k| h[k] = [] }){ |my_hash, cs| value, key = cs.split(":"); my_hash[key.to_sym] << value; my_hash }
my_hash #=> {:state1=>["city1", "city3", "city5"], :state2=>["city2"], :state3=>["city4"]}
I want to programmatically convert this:
{
"a"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
},
"b"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
}
}
to an array like this:
['a/1/A/Standard', 'b/1/A/Standard']
def extract_keys(hash)
return [] unless hash.is_a?(Hash)
hash.each_pair.map {|key, value| [key, extract_keys(value)].join('/') }
end
extract_keys(hash)
=> ["a/1/A/Standard", "b/1/A/Standard"]
From one of my other answers - adapted for your situation. See the link for a more verbose solution to flat_hash
def flat_hash(hash, k = "")
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + '/' + v[0]) }
end
example = {...} # your example hash
foo = flat_hash(example).keys
=> ["/a/1/A/Standard", "/b/1/A/Standard"]
Found this flatten lambda definition.
h = {
"a"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
},
"b"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
}
}
a = []
flatten =
lambda {|r|
(recurse = lambda {|v|
if v.is_a?(Hash)
v.to_a.map{|v| recurse.call(v)}.flatten
elsif v.is_a?(Array)
v.flatten.map{|v| recurse.call(v)}
else
v.to_s
end
}).call(r)
}
h.each do |k,v|
a << k + "/" + flatten.call(v).join("/")
end
Output:
["a/1/A/Standard/true", "b/1/A/Standard/true"]
I don't really know if the title is correct, but the question is quite simple:
I have a value and a key.
The key is as follows:
"one.two.three"
Now, how can I set this hash:
params['one']['two']['three'] = value
You can try to do it with this code:
keys = "one.two.three".split '.' # => ["one", "two", "three"]
params = {}; value = 1; i = 0; # i is an index of processed keys array element
keys.reduce(params) { |hash, key|
hash[key] = if (i += 1) == keys.length
value # assign value to the last key in keys array
else
hash[key] || {} # initialize hash if it is not initialized yet (won't loose already initialized hashes)
end
}
puts params # {"one"=>{"two"=>{"three"=>1}}}
Use recursion:
def make_hash(keys)
keys.empty? ? 1 : { keys.shift => make_hash(keys) }
end
puts make_hash("one.two.three".split '.')
# => {"one"=>{"two"=>{"three"=>1}}}
You can use the inject method:
key = "one.two.three"
value = 5
arr = key.split(".").reverse
arr[1..-1].inject({arr[0] => value}){ |memo, i| {i => memo} }
# => {"one"=>{"two"=>{"three"=>5}}}