Ruby - Elegantly replace hash values with nested value (description) - ruby

The hash I'm working with has a hash for it's values which always contains an ID, name, and description. I am not interested in keeping the ID or name and just want to replace every hash value with its corresponding description.
Code
hsh['nested']['entries']['addr'] = hsh['nested']['entries']['addr']['description']
hsh['nested']['entries']['port'] = hsh['nested']['entries']['port']['description']
hsh['nested']['entries']['protocol'] = hsh['nested']['entries']['protocol']['description']
hsh['nested']['entries']['type'] = hsh['nested']['entries']['type']['description']
... (many more)
This works fine, but it is not very elegant--in reality, I have 20 entries/lines of code to get the job done.
Structure of the hash value (for hsh['nested']['entries']['addr'])
{ "id" => "27", "name" => "Instance", "description" => "**This is what I need.**" }
Taking the first line of code above as a sample, the end result would be the value of hsh['nested']['entries']['addr'] becomes **This is what I need.**
What is an elegant way to achieve this?

hsh = { 'nested'=>
{ 'entries'=>
{
'addr'=>{ "id" => "1", "description"=>"addr" },
'port'=>{ "id" => "2", "description"=>"port" },
'cats'=>{ "id" => "3", "description"=>"dogs" },
'type'=>{ "id" => "4", "description"=>"type" }
}
}
}
keys_to_replace = ["addr", "port", "type"]
hsh['nested']['entries'].tap { |h| keys_to_replace.each { |k| h[k]=h[k]["description"] }
#=> { "addr"=>"addr",
# "port"=>"port",
# "cats"=>{"id"=>"3", "description"=>"dogs"},
# "type"=>"type"
# }
hsh
#=> {"nested"=>
# { "entries"=>
# { "addr"=>"addr",
# "port"=>"port",
# "cats"=>{"id"=>"3", "description"=>"dogs"},
# "type"=>"type"
# }
# }
# }

sub_hash = hsh['nested']['entries']
categories = %w{addr port protocol type}
categories.each do |category|
sub_hash[category] = sub_hash[category]['description']
end

Related

I have json data i need search `unique` if key exist or not

I have JSON data I need search unique if the key exists or not.
[
{
"key1" => []
},
{
"key" => []
},
{
"unique" => []
}
]
I can use loop but need an efficient way to check unique exist or not
You'll need to iterate through the array either way.
# You'll get found item or `nil`
data.find { |item| item.key?('unique') }
# You'll get `true` or `false`
data.any? { |item| item.key?('unique') }
Btw better to use a hash as an input instead of an array:
data = {
"key1" => [],
"key" => [],
"unique" => []
}
data.key?('unique')
=> true

How to loop inside a hash?

If I have a hash like the one below, and I want to loop over the second level keys.
Why does this fail?
hash["Element"].each do |id|
h[id] = hash[id]["Name"]
end
hash
{
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
It fails because using .each on a Hash yields the tuple of key and value.
hash = {
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
}
hash["Element"].each do |id|
p id
end
["499723", {"Name"=>"A"}]
["499725", {"Name"=>"B"}]
Therefore, you need to use
hash["Element"].each do |id, value|
# ...
end
If you don't need the value
hash["Element"].each do |id, _|
# ...
end
However, you can keep the value and access it directly
hash["Element"].each do |id, value|
h[id] = value["Name"]
end
A simple solution in your case is to use Enumberable#each_with_object in combination with the previous information:
hash["Element"].each_with_object({}) do |(id, value), acc|
acc[id] = value["Name"]
end
# => {"499723"=>"A", "499725"=>"B"}
hash["Element"].each.with_object({}) do |(id, subhash), result|
result[id] = subhash["Name"]
end

Return elements from one array, based on values from another one

Writing a plugin for Jekyll, I am stucked at some ruby related code as I am not yet familiar on
how to do .select, .map and all their friends on arrays (except for simple one array cases)
So my problem now is, having this as an array of docs, with their related sha1sum codes:
[
[
#<Jekyll::Document _projects/1st-project.md collection=projects>,
"8f918a3d8263a957c206a9864d3507aaa5277a79"
],
[
#<Jekyll::Document _posts/2nd-project.markdown collection=posts>,
"ca81eda5d49100bdf23db16fe1c9d17040fd33f8"
],
[
#<Jekyll::Document _posts/3rd-project.markdown collection=posts>,
"37bf18464b00c9a808436853bc6d868aff218eb6"
],
...
...
]
And on the other side, the hash of linkage groups like so:
{
"linkage_groups"=>
[
{
"group_name"=>"test1",
"sha1_sums"=> [
"ca81eda5d49100bdf23db16fe1c9d17040fd33f8",
"37bf18464b00c9a808436853bc6d868aff218eb6"
]
},
{
"group_name"=>"test1",
"sha1_sums"=> [
"154c255d59e6063fc609ade2254dc1b09d1de8ab",
"3e9ef9f059786888e39df608d104263cf0fdae76"
]
}
]
}
How would I be able to cycle through these groups one at a time, and return for each group of sha1_sums docs from the above
array where sha1_sum of a document is present in a particular group.
Expected output would be an array of hashes for each of the groups, holding
docs which fulfill the condition, on their sha1_sum being present in a group:
Ex. 2nd and 3rd project fulfill the condition because their sha's are in a group named test1
[
{
"test1" => [#<Jekyll::Document _posts/2nd-project.markdown collection=posts>, #<Jekyll::Document _posts/3rd-project.markdown collection=posts]
},
{
"test2" => [..., ...]
},
{
"test3" => [..., ...]
},
...
]
As of reply from #Lukas Baliak -
Here is what I am getting in case of both hashes belonging to the same group:
{
"ca81eda5d49100bdf23db16fe1c9d17040fd33f8"=>"test1",
"b673be35ad73ab48da23b271ab0dda95ea07c905"=>"test1",
"154c255d59e6063fc609ade2254dc1b09d1de8ab"=>"test2",
"3e9ef9f059786888e39df608d104263cf0fdae76"=>"test2"
}
[
[
#<Jekyll::Document _projects/my-first-project.md collection=projects>,
"b673be35ad73ab48da23b271ab0dda95ea07c905"
],
[
#<Jekyll::Document _posts/2016-06-05-one-more-post.markdown collection=posts>,
"ca81eda5d49100bdf23db16fe1c9d17040fd33f8"
]
]
{
"test1"=> [#<Jekyll::Document _posts/2016-06-05-one-more-post.markdown collection=posts>, "ca81eda5d49100bdf23db16fe1c9d17040fd33f8"]
}
Only one document is listed, why? Where is b673be35ad73ab48da23b271ab0dda95ea07c905?
I preffer to use simple data structures, so i "migrate" groups to Hash
doc
doc = [
[
"#<Jekyll::Document _projects/my-first-project.md collection=projects>",
"8f918a3d8263a957c206a9864d3507aaa5277a79"
],
[
"#<Jekyll::Document _posts/2016-06-05-one-more-post.markdown collection=posts>",
"ca81eda5d49100bdf23db16fe1c9d17040fd33f8"
]
]
group_config
group_config = {
"linkage_groups" => [
{
"group_name" => "test1",
"sha1_sums" => [
"ca81eda5d49100bdf23db16fe1c9d17040fd33f8",
"b673be35ad73ab48da23b271ab0dda95ea07c905"
]
},
{
"group_name" => "test2",
"sha1_sums" => [
"154c255d59e6063fc609ade2254dc1b09d1de8ab",
"8f918a3d8263a957c206a9864d3507aaa5277a79"
]
}
]
}
Migrate to Hash
groups = group_config["linkage_groups"].each_with_object({}) do |h, exp|
h["sha1_sums"].each { |sha1| exp[sha1] = h["group_name"] }
end
export groups
p groups
# {
# "ca81eda5d49100bdf23db16fe1c9d17040fd33f8" => "test1",
# "b673be35ad73ab48da23b271ab0dda95ea07c905" => "test1",
# "154c255d59e6063fc609ade2254dc1b09d1de8ab" => "test2",
# "8f918a3d8263a957c206a9864d3507aaa5277a79" => "test2"
# }
And process to generate hash structure
export = doc.each_with_object({}) do |arr, exp|
exp[groups[arr[1]]] = arr
end
output
p export
# {
# "test2" => ["#<Jekyll::Document _projects/my-first-project.md collection=projects>", "8f918a3d8263a957c206a9864d3507aaa5277a79"],
# "test1" => ["#<Jekyll::Document _posts/2016-06-05-one-more-post.markdown collection=posts>", "ca81eda5d49100bdf23db16fe1c9d17040fd33f8"]
# }
EDIT:
If you need more then one use this modification
export = doc.each_with_object(Hash.new{|k, v| k[v] = []}) do |arr, exp|
exp[groups[arr[1]]] << arr
end
EDIT 2
Ok, if you need one sha1_hash in to more groups may be you can use this update.
groups = group_config["linkage_groups"].each_with_object(Hash.new { |k, v| k[v] = [] }) do |h, exp|
h["sha1_sums"].each { |sha1| exp[sha1] << h["group_name"] }
end
groups
p groups
# {
# "ca81eda5d49100bdf23db16fe1c9d17040fd33f8" => ["test1"],
# "b673be35ad73ab48da23b271ab0dda95ea07c905" => ["test1"],
# "8f918a3d8263a957c206a9864d3507aaa5277a79" => ["test1", "test2"],
# "154c255d59e6063fc609ade2254dc1b09d1de8ab" => ["test2"]
# }
process
export = doc.each_with_object(Hash.new { |k, v| k[v] = [] }) do |arr, exp|
groups[arr[1]].each { |group| exp[group] << arr }
end
output
p export
# {
# "test1" => [
# ["#<Jekyll::Document _projects/my-first-project.md collection=projects>", "8f918a3d8263a957c206a9864d3507aaa5277a79"],
# ["#<Jekyll::Document _posts/2016-06-05-one-more-post.markdown collection=posts>", "ca81eda5d49100bdf23db16fe1c9d17040fd33f8"]
# ],
# "test2" => [
# ["#<Jekyll::Document _projects/my-first-project.md collection=projects>", "8f918a3d8263a957c206a9864d3507aaa5277a79"],
# ["#<Jekyll::Document _posts/2016-06-0s-one-more-post.markdown collection=posts>", "154c255d59e6063fc609ade2254dc1b09d1de8ab"]
# ]
# }
I hope this will helps.
You are not very clear in your question. Sometimes it helps to be detailed about what output is expected for which input. I assume the array contains the Document/Sha1 pairs and that hash contains the linkage_groups.
The hash looks like this:
{"linkage_groups"=>[{"group_name"=>"test1", "sha1_sums"=>["ca81eda5d49100bdf23db16fe1c9d17040fd33f8", "b673be35ad73ab48da23b271ab0dda95ea07c905"]}, {"group_name"=>"test1", "sha1_sums"=>["154c255d59e6063fc609ade2254dc1b09d1de8ab", "3e9ef9f059786888e39df608d104263cf0fdae76"]}]}
And the array looks like this:
[["Document1", "8f918a3d8263a957c206a9864d3507aaa5277a79"], ["Document2", "ca81eda5d49100bdf23db16fe1c9d17040fd33f8"]]
I'd try something like this:
hash["linkage_groups"].each { |group| // for each linkage group
group["sha1_sums"].each { |sha1| // for each sha1 in group
array.each { |array_element| // for each array element (which itself is an array of document/sha1 pairs
if array_element.include?(sha1) then
puts "#{array_element[0]} found in #{group["group_name"]} with #{group["sha1"]}"
end
}
}
}
I leave it up to you to manage how to return the elements according to your needs.

Delete nested hash according to key => value

I have this hash:
response = '{"librairies":[{"id":1,"books":[{"id":1,"qty":1},{"id":2,"qty":3}]},{"id":2,"books":[{"id":1,"qty":0},{"id":2,"qty":3}]}]}'
in which I'd like to delete every librairies where, at least, one of the book quantity is null.
For instance, with this given response, I'd expect this return:
'{"librairies":[{"id":1,"books":[{"id":1,"qty":1},{"id":2,"qty":3}]}]}'
I've tried this:
parsed = JSON.parse(response)
parsed["librairies"].each do |library|
library["books"].each do |book|
parsed.delete(library) if book["qty"] == 0
end
end
but this returns the exact same response hash, without having deleted the second library (the one with id => 2).
You can use Array#delete_if and Enumerable#any? for this
# Move through each array element with delete_if
parsed["librairies"].delete_if do |library|
# evaluates to true if any book hash in the library
# has a "qty" value of 0
library["books"].any? { |book| book["qty"] == 0 }
end
Hope this helps
To avoid changing the hash parsed, you could do the following.
Firstly, let's format parsed so we can see what we're dealing with:
parsed = { "libraries"=>[ { "id"=>1,
"books"=>[ { "id"=>1, "qty"=>1 },
{ "id"=>2, "qty"=>3 } ]
},
{ "id"=>2,
"books"=>[ { "id"=>1, "qty"=>0 },
{ "id"=>2, "qty"=>3 } ]
}
]
}
Later I want to show that parsed has not been changed when we create the new hash. An easy way of doing that is to compute a hash code on parsed before and after, and see if it changes. (While it's not 100% certain that different hashes won't have the same hash code, here it's not something to lose sleep over.)
parsed.hash
#=> 852445412783960729
We first need to make a "deep copy" of parsed so that changes to the copy will not affect parsed. One way of doing that is to use the Marshal module:
new_parsed = Marshal.load(Marshal.dump(parsed))
We can now modify the copy as required:
new_parsed["libraries"].reject! { |h| h["books"].any? { |g| g["qty"].zero? } }
#=> [ { "id"=>1,
# "books"=>[ { "id"=>1, "qty"=>1 },
# { "id"=>2, "qty"=>3 }
# ]
# }
# ]
new_parsed # => { "libraries"=>[ { "id"=>1,
"books"=>[ { "id"=>1, "qty"=>1},
{ "id"=>2, "qty"=>3}
]
}
]
}
And we confirm the original hash was not changed:
parsed.hash
#=> 852445412783960729

Map keys with the same name

A GET to an API endpoint I'm working with returns json with an inconsistent order of contacts, either
{"contacts"=>[
{"id"=>$UUID_0, "name"=>nil, "email"=>$EMAIL_0, "phone"=>$PHONE_0, "type"=>"foo"},
{"id"=>$UUID_1, "name"=>nil, "email"=>$EMAIL_1, "phone"=>$PHONE_1, "type"=>"bar"}
]}
or
{"contacts"=>[
{"id"=>$UUID_1, "name"=>nil, "email"=>$EMAIL_1, "phone"=>$PHONE_1, "type"=>"bar"},
{"id"=>$UUID_0, "name"=>nil, "email"=>$EMAIL_0, "phone"=>$PHONE_0, "type"=>"foo"}
]}
The "type" values are the only static objects in these responses, so I'd like to map this so that the contact types are keys containing the other pairs:
{
"foo"=>{"id"=>$UUID_0, "name"=>$NAME_0, "email"=>$EMAIL_0, "phone"=>$PHONE_0},
"bar"=>{"id"=>$UUID_1, "name"=>$NAME_1, "email"=>$EMAIL_1, "phone"=>$PHONE_1}
}
A solution is not obvious to me.
If you use Ruby on Rails, or at least ActiveSupport, you can try index_by instead of group_by: it won't put the values into arrays.
hash['contacts'].index_by {|r| r['type']}
=>
{
"bar" => {
"id" => "asdf",
"name" => nil,
"email" => "EMAIL_1",
"phone" => "PHONE_1",
"type" => "bar"
},
"foo" => {
"id" => "asdf",
"name" => nil,
"email" => "EMAIL_0",
"phone" => "PHONE_0",
"type" => "foo"
}
}
Hash[data['contacts'].map { |c| [c['type'], c] }]
This can be done with Enumerable#reduce:
hash['contacts'].reduce({}) {|m,c| m[c['type']] = c;m}
How it works:
An empty hash is the starting point.
The block is called once for each element in the contacts list. The block receives the hash that we're building as m and the current contact as c.
In the block, assign c to the hash based on its type and return the hash so far.
Final result is the last return value of the block.

Resources