Consider the Following nested Hash:
data1 = {
"3"=>{"passenger_type"=>"ADT", "the_order"=>"3", "last"=>"JONES", "first"=>"ALENA", "middle"=>nil},
"2"=>{"passenger_type"=>"ADT", "the_order"=>"2", "last"=>"JONES", "first"=>"MAXIM", "middle"=>nil},
"1"=>{"passenger_type"=>"ADTT", "the_order"=>"1", "last"=>"JONES", "first"=>"TODD", "middle"=>nil}}
data2 = {
"3"=>{"first"=>"ALENA", "the_order"=>"3", "middle"=>"", "passenger_type"=>"ADTT", "last"=>"JONES"},
"2"=>{"first"=>"MAXIM", "the_order"=>"2", "middle"=>"", "passenger_type"=>"ADT", "last"=>"JONES"},
"1"=>{"first"=>"TODD", "the_order"=>"1", "middle"=>"", "passenger_type"=>"ADT", "last"=>"JONESS"}}
The Output Should be like this(difference between both hash listed values):
{"3" => {"passenger_type" => ["ADT", "ADTT"]},
"1" => {"passenger_type" => ["ADTT", "ADT"], "last" => ["JONES", "JONESS"]}
Anyone your suggestion is appreciated, thanks in advance.
You can use the form of Hash#merge that takes a block to produce the desired result in a compact manner:
data1.merge(data2) { |_,ho,hn|
ho.merge(hn) { |_,o,n| (o==n||o==''||n=='') ? nil : [o,n] }
.delete_if { |_,v| v==nil } }
.delete_if { |_,v| v.empty? }
#=> {"3"=>{"passenger_type"=>["ADT", "ADTT"]},
# "1"=>{"passenger_type"=>["ADTT", "ADT"], "last"=>["JONES", "JONESS"]}}
Here's some ugly code:
data3 = {}
data1.each do |k, v|
v2 = data2[k]
v.each do |item, val|
if v2.has_key?(item) then
if (val == nil or val == '') and (v2[item] == nil or v2[item] == '') then
next
end
if val != v2[item] then
data3[k] ||= {}
data3[k][item] = [val, v2[item]]
end
end
end
end
puts data3
prints
{"3"=>{"passenger_type"=>["ADT", "ADTT"]}, "1"=>{"passenger_type"=>["ADTT", "ADT"], "last"=>["JONES", "JONESS"]}}
Related
Given I have this hash:
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
And I convert to OpenStruct:
o = OpenStruct.new(h)
=> #<OpenStruct a="a", b="b", c={:d=>"d", :e=>"e"}>
o.a
=> "a"
o.b
=> "b"
o.c
=> {:d=>"d", :e=>"e"}
2.1.2 :006 > o.c.d
NoMethodError: undefined method `d' for {:d=>"d", :e=>"e"}:Hash
I want all the nested keys to be methods as well. So I can access d as such:
o.c.d
=> "d"
How can I achieve this?
You can monkey-patch the Hash class
class Hash
def to_o
JSON.parse to_json, object_class: OpenStruct
end
end
then you can say
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
o = h.to_o
o.c.d # => 'd'
See Convert a complex nested hash to an object.
I came up with this solution:
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
json = h.to_json
=> "{\"a\":\"a\",\"b\":\"b\",\"c\":{\"d\":\"d\",\"e\":\"e\"}}"
object = JSON.parse(json, object_class:OpenStruct)
object.c.d
=> "d"
So for this to work, I had to do an extra step: convert it to json.
personally I use the recursive-open-struct gem - it's then as simple as RecursiveOpenStruct.new(<nested_hash>)
But for the sake of recursion practice, I'll show you a fresh solution:
require 'ostruct'
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
puts to_recursive_ostruct(a: { b: 1}).a.b
# => 1
edit
Weihang Jian showed a slight improvement to this here https://stackoverflow.com/a/69311716/2981429
def to_recursive_ostruct(hash)
hash.each_with_object(OpenStruct.new) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
end
Also see https://stackoverflow.com/a/63264908/2981429 which shows how to handle arrays
note
the reason this is better than the JSON-based solutions is because you can lose some data when you convert to JSON. For example if you convert a Time object to JSON and then parse it, it will be a string. There are many other examples of this:
class Foo; end
JSON.parse({obj: Foo.new}.to_json)["obj"]
# => "#<Foo:0x00007fc8720198b0>"
yeah ... not super useful. You've completely lost your reference to the actual instance.
Here's a recursive solution that avoids converting the hash to json:
def to_o(obj)
if obj.is_a?(Hash)
return OpenStruct.new(obj.map{ |key, val| [ key, to_o(val) ] }.to_h)
elsif obj.is_a?(Array)
return obj.map{ |o| to_o(o) }
else # Assumed to be a primitive value
return obj
end
end
My solution is cleaner and faster than #max-pleaner's.
I don't actually know why but I don't instance extra Hash objects:
def dot_access(hash)
hash.each_with_object(OpenStruct.new) do |(key, value), struct|
struct[key] = value.is_a?(Hash) ? dot_access(value) : value
end
end
Here is the benchmark for you reference:
require 'ostruct'
def dot_access(hash)
hash.each_with_object(OpenStruct.new) do |(key, value), struct|
struct[key] = value.is_a?(Hash) ? dot_access(value) : value
end
end
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
require 'benchmark/ips'
Benchmark.ips do |x|
hash = { a: 1, b: 2, c: { d: 3 } }
x.report('dot_access') { dot_access(hash) }
x.report('to_recursive_ostruct') { to_recursive_ostruct(hash) }
end
Warming up --------------------------------------
dot_access 4.843k i/100ms
to_recursive_ostruct 5.218k i/100ms
Calculating -------------------------------------
dot_access 51.976k (± 5.0%) i/s - 261.522k in 5.044482s
to_recursive_ostruct 50.122k (± 4.6%) i/s - 250.464k in 5.008116s
My solution, based on max pleaner's answer and similar to Xavi's answer:
require 'ostruct'
def initialize_open_struct_deeply(value)
case value
when Hash
OpenStruct.new(value.transform_values { |hash_value| send __method__, hash_value })
when Array
value.map { |element| send __method__, element }
else
value
end
end
Here is one way to override the initializer so you can do OpenStruct.new({ a: "b", c: { d: "e", f: ["g", "h", "i"] }}).
Further, this class is included when you require 'json', so be sure to do this patch after the require.
class OpenStruct
def initialize(hash = nil)
#table = {}
if hash
hash.each_pair do |k, v|
self[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
end
end
end
def keys
#table.keys.map{|k| k.to_s}
end
end
Basing a conversion on OpenStruct works fine until it doesn't. For instance, none of the other answers here properly handle these simple hashes:
people = { person1: { display: { first: 'John' } } }
creds = { oauth: { trust: true }, basic: { trust: false } }
The method below works with those hashes, modifying the input hash rather than returning a new object.
def add_indifferent_access!(hash)
hash.each_pair do |k, v|
hash.instance_variable_set("##{k}", v.tap { |v| send(__method__, v) if v.is_a?(Hash) } )
hash.define_singleton_method(k, proc { hash.instance_variable_get("##{k}") } )
end
end
then
add_indifferent_access!(people)
people.person1.display.first # => 'John'
Or if your context calls for a more inline call structure:
creds.yield_self(&method(:add_indifferent_access!)).oauth.trust # => true
Alternatively, you could mix it in:
module HashExtension
def very_indifferent_access!
each_pair do |k, v|
instance_variable_set("##{k}", v.tap { |v| v.extend(HashExtension) && v.send(__method__) if v.is_a?(Hash) } )
define_singleton_method(k, proc { self.instance_variable_get("##{k}") } )
end
end
end
and apply to individual hashes:
favs = { song1: { title: 'John and Marsha', author: 'Stan Freberg' } }
favs.extend(HashExtension).very_indifferent_access!
favs.song1.title
Here is a variation for monkey-patching Hash, should you opt to do so:
class Hash
def with_very_indifferent_access!
each_pair do |k, v|
instance_variable_set("##{k}", v.tap { |v| v.send(__method__) if v.is_a?(Hash) } )
define_singleton_method(k, proc { instance_variable_get("##{k}") } )
end
end
end
# Note the omission of "v.extend(HashExtension)" vs. the mix-in variation.
Comments to other answers expressed a desire to retain class types. This solution accommodates that.
people = { person1: { created_at: Time.now } }
people.with_very_indifferent_access!
people.person1.created_at.class # => Time
Whatever solution you choose, I recommend testing with this hash:
people = { person1: { display: { first: 'John' } }, person2: { display: { last: 'Jingleheimer' } } }
If you are ok with monkey-patching the Hash class, you can do:
require 'ostruct'
module Structurizable
def each_pair(&block)
each do |k, v|
v = OpenStruct.new(v) if v.is_a? Hash
yield k, v
end
end
end
Hash.prepend Structurizable
people = { person1: { display: { first: 'John' } }, person2: { display: { last: 'Jingleheimer' } } }
puts OpenStruct.new(people).person1.display.first
Ideally, instead of pretending this, we should be able to use a Refinement, but for some reason I can't understand it didn't worked for the each_pair method (also, unfortunately Refinements are still pretty limited)
I have an array with hashes in it. If they have the same key I just want to add its value.
#receivers << result
#receivers
=> [{:email=>"user_02#yorlook.com", :amount=>10.00}]
result
=> {:email=>"user_02#yorlook.com", :amount=>7.00}
I want the result of above to look like this
[{:email=>"user_02#yorlook.com", :amount=>17.00}]
Does anyone know how to do this?
Here is the the entire method
def receivers
#receivers = []
orders.each do |order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result = { email: product_email, amount: amount(order.total_price) }
else
result = { email: product_email, amount: amount(order.total_price, 0.9),
email: outfit_email, amount: amount(order.total_price, 0.1) }
end
#receivers << result
end
end
Using Enumerable#group_by
#receivers.group_by {|h| h[:email]}.map do |k, v|
{email: k, amount: v.inject(0){|s,h| s + h[:amount] } }
end
# => [{:email=>"user_02#yorlook.com", :amount=>17.0}]
Using Enumerable#each_with_object
#receivers.each_with_object(Hash.new(0)) {|h, nh| nh[h[:email]]+= h[:amount] }.map do |k, v|
{email: k, amount: v}
end
# Output: [{ "em#il.one" => 29.0 }, { "em#il.two" => 39.0 }]
def receivers
return #receivers if #receivers
# Produces: { "em#il.one" => 29.0, "em#il.two" => 39.0 }
partial_result = orders.reduce Hash.new(0.00) do |result, order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result[product_email] += amount(order.total_price)
else
result[product_email] += amount(order.total_price, .9)
result[outfit_email] += amount(order.total_price, .1)
end
result
end
#receivers = partial_result.reduce [] do |result, (email, amount)|
result << { email => amount }
end
end
I would just write the code this way:
def add(destination, source)
if destination.nil?
return nil
end
if source.class == Hash
source = [source]
end
for item in source
target = destination.find {|d| d[:email] == item[:email]}
if target.nil?
destination << item
else
target[:amount] += item[:amount]
end
end
destination
end
usage:
#receivers = []
add(#receivers, {:email=>"user_02#yorlook.com", :amount=>10.00})
=> [{:email=>"user_02#yorlook.com", :amount=>10.0}]
add(#receivers, #receivers)
=> [{:email=>"user_02#yorlook.com", :amount=>20.0}]
a = [
{:email=>"user_02#yorlook.com", :amount=>10.0},
{:email=>"user_02#yorlook.com", :amount=>7.0}
]
a.group_by { |v| v.delete :email } # group by emails
.map { |k, v| [k, v.inject(0) { |memo, a| memo + a[:amount] } ] } # sum amounts
.map { |e| %i|email amount|.zip e } # zip to keys
.map &:to_h # convert nested arrays to hashes
From what I understand, you could get away with just .inject:
a = [{:email=>"user_02#yorlook.com", :amount=>10.00}]
b = {:email=>"user_02#yorlook.com", :amount=>7.00}
c = {email: 'user_03#yorlook.com', amount: 10}
[a, b, c].flatten.inject({}) do |a, e|
a[e[:email]] ||= 0
a[e[:email]] += e[:amount]
a
end
=> {
"user_02#yorlook.com" => 17.0,
"user_03#yorlook.com" => 10
}
str = "<a><b><c></c></b></a>"
hash = Hash.from_xml(str)
# => {"a"=>{"b"=>{"c"=>nil}}}
How can I replace all nils in a Hash to "" so that the hash becomes:
{"a"=>{"b"=>{"c"=>""}}}
Here is a recursive method that does not change the original hash.
Code
def denilize(h)
h.each_with_object({}) { |(k,v),g|
g[k] = (Hash === v) ? denilize(v) : v.nil? ? '' : v }
end
Examples
h = { "a"=>{ "b"=>{ "c"=>nil } } }
denilize(h) #=> { "a"=>{ "b"=>{ "c"=>"" } } }
h = { "a"=>{ "b"=>{ "c"=>nil , "d"=>3, "e"=>nil}, "f"=>nil } }
denilize(h) #=> { "a"=>{ "b"=>{ "c"=>"" , "d"=>3, "e"=>""}, "f"=>"" } }
this will destroy the original hash and will not work with hashes with infinite recursion.
def nil2empty(hash)
hash.keys.each do |key|
if hash[key].kind_of? Hash
nil2empty(hash[key])
else
hash[key] = '' if hash[key].nil?
end
end
true # of course, what else? :P
end
example of usage:
hash
=> {"a"=>{"b"=>{"c"=>nil}}}
nil2empty(hash)
=> true
hash
=> {"a"=>{"b"=>{"c"=>""}}}
I know this is not the answer you are expecting, but if you could handle a value instead of "" , this code works
eval({"a"=>{"b"=>{"c"=>nil}}}.to_s.gsub("nil", "1")) #=> returns a hash #{"a"=>{"b"=>{"c"=>1}}}
What's a convenient way to get a list of all Hash keys (with nesting) separated by dots?
Given I have a hash:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}
Desired output (array of strings) which represents all key paths in a hash:
level1.level21.level31
level1.level21.level32
level1.level22
My current solution:
class HashKeysDumper
def self.dump(hash)
hash.map do |k, v|
if v.is_a? Hash
keys = dump(v)
keys.map { |k1| [k, k1].join('.') }
else
k.to_s
end
end.flatten
end
end
It also available as gist (with specs).
Well, it depends on what you mean by cleaner, but here's a smaller version that…
Will work on subclasses Hashes or Hash-alikes
Extends Hash, making it look cleaner in your code.
class Hash
def keydump
map{|k,v|v.keydump.map{|a|"#{k}.#{a}"} rescue k.to_s}.flatten
end
end
results:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}.keydump
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Here is my vision of this:
h = { 'level1' => { 'level2' => { 'level31' => 'val1', 'level32' => 'val2' } } }
class Hash
def nested_keys
self.inject([]) { |f, (k,v)| f += [k, v.is_a?(Hash) ? v.nested_keys : []] }.flatten
end
end
keys = h.nested_keys
p keys
#=> ["level1", "level2", "level31", "level32"]
k1, k2 = keys.shift, keys.shift
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level31
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level32
Here is a Working Demo
I just committed some code to RubyTree that adds from_hash() which would allow you to do this:
require 'rubytree'
Tree::TreeNode.from_hash(hash).each_leaf.map{|n| "#{n.name}.#{n.parentage.map(&:name).reverse.join('.')}" }
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Aside from the gem require, it's a one-liner :)
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"]