I have a simple hash like so { "1234" => "5", "2345" => "6" }
How can I create a new hash with both the keys and values in side it? Like so:
{ key_id = "1234", value_id = "5" }, { key_id = "2345", value_id = "6" }
What are you actually trying to achieve with this? If you're looking to iterate over all of the keys, you can use .keys:
h = { "1234" => "5", "2345" => "6" }
h.keys
=> ["1234", "2345"]
If you want to just create an array of hashes, you should be able to iterate over the keys:
h = { "1234" => "5", "2345" => "6" }
a = []
h.each {|k, v| a << {:key_id => k, :value_id => v}
By "merging" two hashes, I think you mean to put all the contents of two different hashes into one new hash. Because the keys of a hash must be unique, if the same key exists in the both source hashes, only one value can survive.
In this example, I merge the contents of hash x and hash y into hash z. The values in y will overwrite the values in z if there are any duplicate keys.
x = { "a" => "1","b" => "2","c" => "3" }
y = { "c" => "999","d" => "4","e" => "5" }
z = {}
x.each do |key,value|
z[key] = value
end
y.each do |key,value|
z[key] = value
end
The source hashes had a total of 6 keys. Because the key "c" was in both hases, the merged hash only has 5 keys.
=> {"a"=>"1", "b"=>"2", "c"=>"999", "d"=>"4", "e"=>"5"}
You can loop through each pair of the original hash and build up an array of hashes:
hashes = []
{ "1234" => "5", "2345" => "6" }.each_pair {|key, value| hashes << { :key_id => key, :value_id => value } }
Will yield:
[{:key_id=>"2345", :value_id=>"6"}, {:key_id=>"1234", :value_id=>"5"}]
What should the keys be for the derived hash, the same at the original? In that case use this snippet:
x = { "1234" => "5", "2345" => "6" }
y = {}
x.each do |key, value|
y[key] = { "key_id" => key, "value_id" => value }
end
Related
Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to search for a string that matches buyer35. On match, I want it to return the following result
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}]
All, other non matching keys,values, arrays should be omitted. I have the following example, but it doesn't quite work
def search(hash)
hash.each_with_object({}) do |(key, value), obj|
if value.is_a?(Hash)
returned_hash = search(value)
obj[key] = returned_hash unless returned_hash.empty?
elsif value.is_a?(Array)
obj[key] = value if value.any? { |v| matches(v) }
elsif matches(key) || matches(value)
obj[key] = value
end
end
end
def matches(str)
match_criteria = /#{Regexp.escape("buyer35")}/i
(str.is_a?(String) || str == true || str == false) && str.to_s.match?(match_criteria)
end
....
=> search(hash_or_array)
Any help is appreciated. I realize, I need to use recursion, but can't quite figure how to build/keep track of the matched node from the parent node.
You can use the following recursive method.
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
recurse(hash_or_array, "buyer35")
#=> [{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}]
recurse(hash_or_array, "buyer24")
#=>[{"book2"=>{"book22"=>"buyer24"}}]
recurse(hash_or_array, "buyer33")
#=> [{"book3"=>{"2"=>"buyer33"}}]
recurse(hash_or_array, 11)
#=> [{"book3"=>{"3"=>[{"5"=>11}]}}]
recurse(hash_or_array, "buyer5")
#=>[{"book5"=>{"book5,1"=>"buyer5"}}]
If desired, one may write, for example,
recurse(hash_or_array, "buyer35").first
#=> {"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}
Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to look for the string buyer35. Upon match, it should return the following
[
{
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
}
},
{
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
}
]
The solution below (from another SO question, link below), returns the first match, but I am trying to figure out how to return multiple matches
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
This is the original question and answer: How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?
UPDATE: The correct return format should be
[
{
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}
},
{
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
}
]
Here is a function that recursively searches for the target value in any nested arrays or hashes. The function is then used to select the entries in your top level hash_or_array for entries that contain the target and adds them to an array.
require 'pp'
def find_value(obj, target, found = false)
found = true if obj == target
return found unless obj.is_a?(Array) || obj.is_a?(Hash)
case obj
when Hash
obj.each { |k, v| found = find_value(v, target, found) }
when Array
obj.each { |v| found = find_value(v, target, found) }
end
found
end
found_entries = hash_or_array.inject([]) {|entries, obj| entries << obj.select { |k, v| find_value({ k => v }, "buyer35") }}
pp found_entries
=>
[{"book3"=>
{"0"=>"buyer31",
"1"=>"buyer32",
"2"=>"buyer33",
"3"=>[{"4"=>"buyer34", "5"=>[10, 11], "6"=>[{"7"=>"buyer35"}]}]},
"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]
Here is a recursive solution to your "real" question. Since it mutates the original object, I used a "trick" to make a deep copy first. The keep_entries_with produces an object with the original shape as your input, but since your output shape is different the second step just transforms the filtered result into the shape of your desired output.
deep_copy = Marshal.load(Marshal.dump(hash_or_array))
def keep_entries_with(target, obj)
return unless obj.is_a?(Hash) || obj.is_a?(Array)
case obj
when Hash
keep_entries_with(target, obj.values)
obj.keep_if do |k, v|
v == target ||
v.is_a?(Hash) && v.values.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) } ||
v.is_a?(Array) && v.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) }
end
when Array
obj.each do |v|
keep_entries_with(target, v)
end
end
end
filtered = keep_entries_with("buyer35", deep_copy)
final_result = filtered.first.map { |k, v| { k => v } }
pp final_result
which produces:
[{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}},
{"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]
The code below appears to generate the desired output for this specific case:
hash_or_array.inject([]) do |result, x|
x.keep_if { |k, v| v.to_s =~ /buyer35/ }
result << x
end
I need to modify a hash of hash and convert it in a hash of array.
I also need to add a new key value.
This is my current hash:
{ "132552" => {
"name" => "Paul",
"id" => 53
},
"22478" => {
"name" => "Peter",
"id" => 55
}
}
I expect the output to be like this:
[
{
"person_id": "132552",
"name" => "Paul",
"id" => 53
},
{
"person_id": "22478",
"name" => "Peter",
"id" => 55
}
]
You could map with Enumerable#map to hash values merging (Hash#merge) the new pairs:
original_hash.map { |k,v| v.merge({ "person_id" => k }) }
#=> [{"name"=>"Paul", "id"=>53, "person_id"=>"132552"}, {"name"=>"Peter", "id"=>55, "person_id"=>"22478"}]
Probably not the best solution but the following should work (considering h is your hash):
#h.each do |key,value|
value["person_id"] = key
end
#array = []
#h.each do |key, value|
#array << value
end
This is a perfect fit for Enumerable#each_with_object.
output = input.each_with_object([]) do |(person_id, values), array|
array << values.merge("person_id" => person_id)
end
This method takes an arbitrary object for our initial state (here an array), iterate the collection (our hash) with a block. The initial object is yield as second argument of the block. At each iteration we're populating the array as we want. At the end of the block this object is returned, in output variable in my example.
Note that in my example, I destructure the hash into (person_id, values) : each entry of an hash can be destructed as (key, values) into block/method arguments.
Enumerable#each_with_object
I want to loop through a hash that contains a mix of nested object types. E.g.
{
"a" => "1",
"b" => ["1", "2"],
"c" => {"d" => "1", "e" => {"f" => ["1", "2", {"g" => ["x", "y", "1"]}]}}
}
and transform all string values to integers, given that they consist of numbers only. So they should match /^\d+$/.
But the important part is the iteration part.
How do I find all the values of key/value pairs as well as all values inside arrays, so I can manipulate them?
It's a bit similar to the deep_symbolize_keys method, only here I'm interested in everything else that hash keys.
My own implementation goes like this, but it's long and I may not have catched all edge cases:
def all_to_i(x)
if x.is_a?(Hash)
x.each do |k, v|
if v.is_a?(Hash)
all_to_i(v)
elsif v.is_a?(Array)
x[k] = v.map { |v2| all_to_i(v2) }
elsif v.is_a?(String)
if v.scan(/^\d+$/) != []
x[k] = v.to_i
else
x[k] = v
end
end
end
elsif x.is_a?(Array)
x.map { |x2| all_to_i(x2) }
elsif x.is_a?(String)
if x.scan(/^\d+$/) != []
x.to_i
else
x
end
end
end
# => {"a"=>1, "b"=>[1, 2], "c"=>{"d"=>1, "e"=>{"f"=>[1, 2, {"g"=>["x", "y", 1]}]}}}
I have a hash in ruby which looks something like this:
{
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
I am trying to lookout for a solutions which can cut this hash in to two parts, one with value as 1 and other hash with value as 0.
You can group hash by its value:
h1 = {
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
h2 = h1.group_by{|k,v| v}
It will produce a hash grouped by its values like this:
h2 = {"1"=>[["admin_milestones", "1"], ["admin_goals", "1"], ["admin_tasks", "1"], ["admin_messages", "1"], ["admin_meetings", "1"]],
"0"=>[["users_milestones", "0"], ["users_goals", "0"], ["users_tasks", "0"], ["users_messages", "0"], ["users_meetings", "0"]]}
If you want an array as answer the cleanest solution is the partition method.
zeros, ones = my_hash.partition{|key, val| val == '0'}
You should use group_by on the keys arrays and use the value as the grouping element:
h1 = {
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
# group_by on the keys, then use the value from the hash as bucket
h2 = h1.keys.group_by { |k| h1[k] }
puts h2.inspect
Returns a hash from value to array of keys:
{
"1" => [
[0] "admin_milestones",
[1] "admin_goals",
[2] "admin_tasks",
[3] "admin_messages",
[4] "admin_meetings"
],
"0" => [
[0] "users_milestones",
[1] "users_goals",
[2] "users_tasks",
[3] "users_messages",
[4] "users_meetings"
]
}
Just Hash.select:
h1.select { |key, value| value == '0' } #=> {"users_milestones"=>"0", "users_goals"=>"0", ...}
h1.select { |key, value| value == '1' } #=> {"admin_milestones"=>"1", "admin_goals"=>"1", ...}
The return value depends on your Ruby version. Ruby 1.8 returns a array of arrays, whereas Ruby 1.9 returns a hash like in the example above.
Similar with https://stackoverflow.com/a/56164608/14718545 you can use group_by but with then, in this case, you will avoid instantiating an extra variable.
{
"admin_milestones" => "1",
"users_milestones" => "0",
"admin_goals" => "1",
"users_goals" => "0",
"admin_tasks" => "1",
"users_tasks" => "0",
"admin_messages" => "1",
"users_messages" => "0",
"admin_meetings" => "1",
"users_meetings" => "0"
}.then { |h| h.keys.group_by { |k| h[k] } }
{"1"=>["admin_milestones", "admin_goals", "admin_tasks", "admin_messages", "admin_meetings"],
"0"=>["users_milestones", "users_goals", "users_tasks", "users_messages", "users_meetings"]}