how to push multiple values to same hash key - ruby

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"]}

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}

Hash default value is a hash with same default value

Setting a default value of a hash like this:
hash = Hash.new { |hsh, key| hsh[key] = {} }
will create (and assign) a new hash for an unknown key, but will return nil for an unknown key of the created hash:
hash[:unkown_key] #=> {}
hash[:unkown_key][:nested_unknown] #=> nil
I could make it work for the second level like this:
hash = Hash.new do |hsh, key|
hsh[key] = Hash.new { |nest_hsh, nest_key| nest_hsh[nest_key] = {} }
end
but, it does not work at the third level:
hash[:unkown_key][:nested_unknown] #=> {}
hash[:unkown_key][:nested_unknown][:third_level] #=> nil
How can I make it work at arbitrary levels?
hash[:unkown_key][:nested_unknown][:third_level][...][:nth_level] #=> {}
Sort of mind bending, but you can pass the hash's default_proc to the inner hash:
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
hash[:foo] #=> {}
hash[:foo][:bar] #=> {}
hash[:foo][:bar][:baz] #=> {}
hash #=> {:foo=>{:bar=>{:baz=>{}}}}
bottomless_hash = ->() do
Hash.new { |h, k| h[k] = bottomless_hash.call }
end
hash = bottomless_hash.call
hash[:unkown_key][:nested_unknown][:third_level][:fourth] # => {}
You could create method that will do this with Recursion
class Hash
def self.recursive
new { |hash, key| hash[key] = recursive }
end
end
hash = Hash.recursive
hash[:unknown_key] # => {}
hash[:first_unknown_key][:second_unknown_key][...][:infinity]
# hash => {first_unknown_key: {second_unknown_key: {... {infinity: {}}}}}

Altering Hashes

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}

String to array to multidimensional hash in ruby

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}}}

Ruby Inserting Key, Value elements in Hash

I want to add elements to my Hash lists, which can have more than one value. Here is my code. I don't know how I can solve it!
class dictionary
def initialize(publisher)
#publisher=publisher
#list=Hash.new()
end
def []=(key,value)
#list << key unless #list.has_key?(key)
#list[key] = value
end
end
dic = Dictionary.new
dic["tall"] = ["long", "word-2", "word-3"]
p dic
Many thanks in advance.
regards,
koko
I think this is what you're trying to do
class Dictionary
def initialize()
#data = Hash.new { |hash, key| hash[key] = [] }
end
def [](key)
#data[key]
end
def []=(key,words)
#data[key] += [words].flatten
#data[key].uniq!
end
end
d = Dictionary.new
d['tall'] = %w(long word1 word2)
d['something'] = %w(anything foo bar)
d['more'] = 'yes'
puts d.inspect
#=> #<Dictionary:0x42d33c #data={"tall"=>["long", "word1", "word2"], "something"=>["anything", "foo", "bar"], "more"=>["yes"]}>
puts d['tall'].inspect
#=> ["long", "word1", "word2"]
Edit
Now avoids duplicate values thanks to Array#uniq!.
d = Dictionary.new
d['foo'] = %w(bar baz bof)
d['foo'] = %w(bar zim) # bar will not be added twice!
puts d.inspect
#<Dictionary:0x42d48c #data={"foo"=>["bar", "baz", "bof", "zim"]}>
Probably, you want to merge two Hashes?
my_hash = { "key1"=> value1 }
another_hash = { "key2"=> value2 }
my_hash.merge(another_hash) # => { "key1"=> value1, "key2"=> value2 }

Resources