Ruby: Swap out hash key with matching value from other hash - ruby

I have these hashes:
hash = {1 => "popcorn", 2 => "soda"}
other_hash = {1 => "dave", 2 => "linda", 3 => "bobby_third_wheel"}
I would like to replace the id reference with the name associated to the id in the second hash, if there is a record in other_hash that has nothing to match, it should just be dropped in the resulting hash. Like this:
the_one_hash_to_rule_them_all = {"dave" => "popcorn", "linda" => "soda"}

You can easly use this each_with_object method on "primary" hash with names.
other_hash.each_with_object({}) { |(id, name), h| h[name] = hash[id] if hash.key?(id) }
# => {"dave"=>"popcorn", "linda"=>"soda"}

hash.each_with_object({}){|(k,v), res| res[other_hash[k]] = v}
# => {"dave"=>"popcorn", "linda"=>"soda"}

First, an "array-comprehension" with the pattern enumerable.map { expr if condition }.compact and finally an Array#to_h.
h = other_hash.map { |k, v| [v, hash[k]] if hash.has_key?(k) }.compact.to_h
#=> {"dave"=>"popcorn", "linda"=>"soda"}
Also:
h = other_hash.select { |k, v| hash.has_key?(k) }.map { |k, v| [v, hash[k]] }.to_h

hash.map{|k, v| [other_hash[k], v]}.to_h
# => {"dave"=>"popcorn", "linda"=>"soda"}

Related

Array as key for a Hash. How to access values, how to format properly?

t = {
"Bachelor" => [1,2,3],
"Master" => [4,5,6]
}
r = t.invert
=> {[1, 2, 3]=>"Bachelor", [4, 5, 6]=>"Master"}
r[1]
=> nil
My question is, how do I access the value for key 1,2 etc in the example mentioned?
How to convert this to: {"1"=> "Bachelor","2"=> "Bachelor"..} etc?
res = t.each_with_object({}) do |(key, vals), res|
vals.each do |val|
res[val] = key
end
end
res[1]
=> "Bachelor"
res
=> {1=>"Bachelor", 2=>"Bachelor", 3=>"Bachelor", 4=>"Master", 5=>"Master", 6=>"Master"}
You could give your hash a default procedure which runs whenever the key can't be found:
t = {
"Bachelor" => [1,2,3],
"Master" => [4,5,6]
}
r = t.invert
r.default_proc = proc do |hash, key|
hash.find{|k,v| k.include?(key) }.last
end
#demo:
puts r[5] # => Master
Another useful mechanism which does not use a hash is a case when construct:
num=5
title = case num
when 1..3 then "Bachelor"
when 4..6 then "Master"
end
You could create a new hash by iterating through the array keys of t.invert:
h = t.invert.map do |k,v|
k.map {|kk| {kk => v} } }.flatten.inject(:merge)
=> {1=>"Bachelor", 2=>"Bachelor", 3=>"Bachelor", 4=>"Master", 5=>"Master", 6=>"Master"}
h[1]
=> "Bachelor"
res = {}
t.each { |key, values| values.inject(res) { |h, i| h[i] = key; h }}
res[1]
=> "Bachelor"
res
=> {1=>"Bachelor", 2=>"Bachelor", 3=>"Bachelor", 4=>"Master", 5=>"Master", 6=>"Master"}

Add values of an array inside a hash

I created a hash that has an array as a value.
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
I want the sum of all values in each array. I expect to have such output as:
{
"0":[24],
"1":[14],
"2":[23],
"3":[23],
"4":[34],
"5":[47],
"6":[12],
"7":[],
"8":[26],
"9":[39],
"10":[23],
"11":[27]
}
I do not know how to proceed from here. Any pointers are thankful.
I would do something like this:
hash = { "0" => [0,14,0,14], "1" => [0,14], "7" => [] }
hash.each { |k, v| hash[k] = Array(v.reduce(:+)) }
# => { "0" => [28], "1" => [14], "7" => [] }
For hash object you as this one.
hash = {"0"=>[0,14,0,14],"1"=>[0,14],"2"=>[0,11,0,12],"3"=>[0,11,0,12],"4"=>[0,10,0,13,0,11],"5"=>[0,10,0,14,0,0,0,11,12,0],"6"=>[0,0,12],"7"=>[],"8"=>[0,14,0,12],"9"=>[0,14,0,0,11,14],"10"=>[0,11,0,12],"11"=>[0,13,0,14]}
You could change value of each k => v pair
hash.each_pair do |k, v|
hash[k] = [v.reduce(0, :+)]
end
will resolve in
hash = {"0"=>[28], "1"=>[14], "2"=>[23], "3"=>[23], "4"=>[34], "5"=>[47], "6"=>[12], "7"=>[0], "8"=>[26], "9"=>[39], "10"=>[23], "11"=>[27]}
If your string is just like you mentioned you can parse it using JSON, or jump that step if you have already an Hash.
You can check the documentation of inject here
require 'json'
json_string = '
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
'
hash = JSON.parse json_string
result = Hash.new
hash.each do |key, value|
result[key] = value.inject(:+)
end
puts result.inspect
and the result:
{"0"=>28, "1"=>14, "2"=>23, "3"=>23, "4"=>34, "5"=>47, "6"=>12, "7"=>nil, "8"=>26, "9"=>39, "10"=>23, "11"=>27}
Classic example for map/reduce. You need to iterate on the hash keys and values (map {|k,v|}), and for each value count the sum using reduce(0) {|acc, x| acc+x}
h = {1 => [1,2,3], 2 => [3,4,5], 7 => []} #example
a = h.map {|k,v| [k ,v.reduce(0) {|acc, x| acc+x}] }
=> [[1, 6], [2, 12], [7, 0]]
Hash[a]
=> {1=>6, 2=>12, 7=>0}

how can I programmatically identify which keys have sub key-value-pairs in a JSON doc? [duplicate]

This question already has answers here:
Flattening nested hash to a single hash with Ruby/Rails
(6 answers)
Closed 8 years ago.
I fetch a JSON document and need to programmatically "flatten" the keys for another third-party service.
What this means is, if my JSON doc comes back with the following:
{'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
I need to be able to know to create a "flat" key-value pair for a third-party service like this:
first_name = "Joe"
hoffman.patterns = "negativity, self-sabotage"
hoffman.right_road = "happy family"
mbti = "INTJ"
Once I know there's a sub-document, the parsing I think I have figured out just appending the sub-keys with key + '.' + "{subkey}" but right now, don't know which ones are straight key-value and which one's have sub-documents.
Question:
a) How can I parse the JSON to know which keys have sub-documents (additional key-values)?
b) Suggestions on ways to create a string from an array
You could also monkey patch Hash to do this on it's own like so:
class Hash
def flatten_keys(prefix=nil)
each_pair.map do |k,v|
key = [prefix,k].compact.join(".")
v.is_a?(Hash) ? v.flatten_keys(key) : [key,v.is_a?(Array) ? v.join(", ") : v]
end.flatten.each_slice(2).to_a
end
def to_flat_hash
Hash[flatten_keys]
end
end
Then it would be
require 'json'
h = JSON.parse(YOUR_JSON_RESPONSE)
#=> {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Will work with additional nesting too
h = {"first_name"=>"Joe", "hoffman"=>{"patterns"=>["negativity", "self-sabotage"], "right_road"=>"happy family", "wrong_road"=>{"bad_choices"=>["alcohol", "heroin"]}}, "mbti"=>"INTJ"}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "hoffman.wrong_road.bad_choices"=>"alcohol, heroin", "mbti"=>"INTJ"}
Quick and dirty recursive proc:
# assuming you've already `JSON.parse` the incoming json into this hash:
a = {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
# define a recursive proc:
flatten_keys = -> (h, prefix = "") do
#flattened_keys ||= {}
h.each do |key, value|
# Here we check if there's "sub documents" by asking if the value is a Hash
# we also pass in the name of the current prefix and key and append a . to it
if value.is_a? Hash
flatten_keys.call value, "#{prefix}#{key}."
else
# if not we concatenate the key and the prefix and add it to the #flattened_keys hash
#flattened_keys["#{prefix}#{key}"] = value
end
end
#flattened_keys
end
flattened = flatten_keys.call a
# => "first_name"=>"Joe", "hoffman.patterns"=>["negativity", "self-sabotage"], "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
And then, to turn the arrays into strings just join them:
flattened.inject({}) do |hash, (key, value)|
value = value.join(', ') if value.is_a? Array
hash.merge! key => value
end
# => {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Another way, inspired by this post:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
result = {}
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
.each{ |k, v| result[k.join('.')] = v } #=> {"a.b.c"=>1, "a.b.d"=>2, "a.e"=>3, "f"=>4}

How do I convert a Ruby hash so that all of its keys are symbols?

I have a Ruby hash which looks like:
{ "id" => "123", "name" => "test" }
I would like to convert it to:
{ :id => "123", :name => "test" }
hash = {"apple" => "banana", "coconut" => "domino"}
Hash[hash.map{ |k, v| [k.to_sym, v] }]
#=> {:apple=>"banana", :coconut=>"domino"}
#mu is too short: Didn't see word "recursive", but if you insist (along with protection against non-existent to_sym, just want to remind that in Ruby 1.8 1.to_sym == nil, so playing with some key types can be misleading):
hash = {"a" => {"b" => "c"}, "d" => "e", Object.new => "g"}
s2s =
lambda do |h|
Hash === h ?
Hash[
h.map do |k, v|
[k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]]
end
] : h
end
s2s[hash] #=> {:d=>"e", #<Object:0x100396ee8>=>"g", :a=>{:b=>"c"}}
If you happen to be in Rails then you'll have symbolize_keys:
Return a new hash with all keys converted to symbols, as long as they respond to to_sym.
and symbolize_keys! which does the same but operates in-place. So, if you're in Rails, you could:
hash.symbolize_keys!
If you want to recursively symbolize inner hashes then I think you'd have to do it yourself but with something like this:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.to_sym
h[ks] = h.delete k
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
You might want to play with the kind_of? Hash to match your specific circumstances; using respond_to? :keys might make more sense. And if you want to allow for keys that don't understand to_sym, then:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
h[ks] = h.delete k # Preserve order even when k == ks
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
Note that h[ks] = h.delete k doesn't change the content of the Hash when k == ks but it will preserve the order when you're using Ruby 1.9+. You could also use the [(key.to_sym rescue key) || key] approach that Rails uses in their symbolize_keys! but I think that's an abuse of the exception handling system.
The second symbolize_keys_deep! turns this:
{ 'a' => 'b', 'c' => { 'd' => { 'e' => 'f' }, 'g' => 'h' }, ['i'] => 'j' }
into this:
{ :a => 'b', :c => { :d => { :e => 'f' }, :g => 'h' }, ['i'] => 'j' }
You could monkey patch either version of symbolize_keys_deep! into Hash if you really wanted to but I generally stay away from monkey patching unless I have very good reasons to do it.
If you are using Rails >= 4 you can use:
hash.deep_symbolize_keys
hash.deep_symbolize_keys!
or
hash.deep_stringify_keys
hash.deep_stringify_keys!
see http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys
Just in case you are parsing JSON, from the JSON docs you can add the option to symbolize the keys upon parsing:
hash = JSON.parse(json_data, symbolize_names: true)
Victor Moroz provided a lovely answer for the simple recursive case, but it won't process hashes that are nested within nested arrays:
hash = { "a" => [{ "b" => "c" }] }
s2s[hash] #=> {:a=>[{"b"=>"c"}]}
If you need to support hashes within arrays within hashes, you'll want something more like this:
def recursive_symbolize_keys(h)
case h
when Hash
Hash[
h.map do |k, v|
[ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
end
]
when Enumerable
h.map { |v| recursive_symbolize_keys(v) }
else
h
end
end
Try this:
hash = {"apple" => "banana", "coconut" => "domino"}
# => {"apple"=>"banana", "coconut"=>"domino"}
hash.tap do |h|
h.keys.each { |k| h[k.to_sym] = h.delete(k) }
end
# => {:apple=>"banana", :coconut=>"domino"}
This iterates over the keys, and for each one, it deletes the stringified key and assigns its value to the symbolized key.
If you're using Rails (or just Active Support):
{ "id" => "123", "name" => "test" }.symbolize_keys
Starting with Ruby 2.5 you can use the transform_key method.
So in your case it would be:
h = { "id" => "123", "name" => "test" }
h.transform_keys!(&:to_sym) #=> {:id=>"123", :name=>"test"}
Note: the same methods are also available on Ruby on Rails.
Here's a Ruby one-liner that is faster than the chosen answer:
hash = {"apple" => "banana", "coconut" => "domino"}
#=> {"apple"=>"banana", "coconut"=>"domino"}
hash.inject({}){|h,(k,v)| h[k.intern] = v; h}
#=> {:apple=>"banana", :coconut=>"domino"}
Benchmark results:
n = 100000
Benchmark.bm do |bm|
bm.report { n.times { hash.inject({}){|h,(k,v)| h[k.intern] = v; h} } }
bm.report { n.times { Hash[hash.map{ |k, v| [k.to_sym, v] }] } }
end
# => user system total real
# => 0.100000 0.000000 0.100000 ( 0.107940)
# => 0.120000 0.010000 0.130000 ( 0.137966)
I'm partial to:
irb
ruby-1.9.2-p290 :001 > hash = {"apple" => "banana", "coconut" => "domino"}
{
"apple" => "banana",
"coconut" => "domino"
}
ruby-1.9.2-p290 :002 > hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
{
:apple => "banana",
:coconut => "domino"
}
This works because we're iterating over the hash and building a new one on the fly. It isn't recursive, but you could figure that out from looking at some of the other answers.
hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
You can also extend core Hash ruby class placing a /lib/hash.rb file :
class Hash
def symbolize_keys_deep!
new_hash = {}
keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
if values_at(k).first.kind_of? Hash or values_at(k).first.kind_of? Array
new_hash[ks] = values_at(k).first.send(:symbolize_keys_deep!)
else
new_hash[ks] = values_at(k).first
end
end
new_hash
end
end
If you want to make sure keys of any hash wrapped into arrays inside your parent hash are symbolized, you need to extend also array class creating a "array.rb" file with that code :
class Array
def symbolize_keys_deep!
new_ar = []
self.each do |value|
new_value = value
if value.is_a? Hash or value.is_a? Array
new_value = value.symbolize_keys_deep!
end
new_ar << new_value
end
new_ar
end
end
This allows to call "symbolize_keys_deep!" on any hash variable like this :
myhash.symbolize_keys_deep!
def symbolize_keys(hash)
new={}
hash.map do |key,value|
if value.is_a?(Hash)
value = symbolize_keys(value)
end
new[key.to_sym]=value
end
return new
end
puts symbolize_keys("c"=>{"a"=>2,"k"=>{"e"=>9}})
#{:c=>{:a=>2, :k=>{:e=>9}}}
Here's my two cents,
my version of symbolize_keys_deep! uses the original symbolize_keys! provided by rails and just makes a simple recursive call to Symbolize sub hashes.
def symbolize_keys_deep!(h)
h.symbolize_keys!
h.each do |k, v|
symbolize_keys_deep!(v) if v.is_a? Hash
end
end
Facets' Hash#rekey is also a worth mentioning.
Sample:
require 'facets/hash/rekey'
{ "id" => "123", "name" => "test" }.deep_rekey
=> {:id=>"123", :name=>"test"}
There is also a recursive version:
require 'facets/hash/deep_rekey'
{ "id" => "123", "name" => {"first" => "John", "last" => "Doe" } }.deep_rekey
=> {:id=>"123", :name=>{:first=>"John", :last=>"Doe"}}
Here's a little recursive function to do a deep symbolization of the keys:
def symbolize_keys(hash)
Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
end

Hash.each doesn't return a hash?

Hash.each returns an array [key, value],
but if I want a hash?
Example: {:key => value }
I'm assuming you meant "yields" where you said "return" because Hash#each already returns a hash (the receiver).
To answer your question: If you need a hash with the key and the value you can just create one. Like this:
hash.each do |key, value|
kv_hash = {key => value}
do_something_with(kv_hash)
end
There is no alternative each method that yields hashs, so the above is the best you can do.
I think you are trying to transform the hash somehow, so I will give you my solution to this problem, which may be not exactly the same. To modify a hash, you have to .map them and construct a new hash.
This is how I reverse key and values:
h = {:a => 'a', :b => 'b'}
Hash[h.map{ |k,v| [v, k] }]
# => {"a"=>:a, "b"=>:b}
Call .each with two parameters:
>> a = {1 => 2, 3 => 4}
>> a.each { |b, c|
?> puts "#{b} => #{c}"
>> }
1 => 2
3 => 4
=> {1=>2, 3=>4}
You could map the hash to a list of single-element hashes, then call each on the list:
h = {:a => 'a', :b => 'b'}
h.map{ |k,v| {k => v}}.each{ |x| puts x }

Resources