How to create Hash with duplicate value count from JSON file - ruby

I have a JSON file being generated from my console and would like to parse it in order to see how many nodes I have running on what version and on which operating system.
Example JSON:
[
{
"facts.aio_agent_version": "7.9.0",
"facts.operatingsystem": "windows"
},
{
"facts.aio_agent_version": "7.8.0",
"facts.operatingsystem": "windows"
},
{
"facts.aio_agent_version": "7.9.0",
"facts.operatingsystem": "CentOS"
},
{
"facts.aio_agent_version": "7.9.0",
"facts.operatingsystem": "CentOS"
},
{
"facts.aio_agent_version": "7.8.0",
"facts.operatingsystem": "CentOS"
}
]
I need an output of a ruby hash:
{"CentOS"=>"7.8.0"=>"nodes"=> 1, "CentOS"=>"7.9.0"=>"nodes"=> 2, "windows"=>"7.8.0"=>"nodes"=> 1, "windows"=>"7.9.0"=>"nodes"=> 1}
This is as far as I've managed to get:
require 'json'
file = File.read('./data.json')
hash = JSON.parse(file)
hash2 = {}
hash.each { |key|
if !hash2.key?(key["facts.operatingsystem"]=>key["facts.aio_agent_version"])
hash2[key["facts.operatingsystem"]] = key["facts.aio_agent_version"] = "node"
else
hash2[key["facts.operatingsystem"]["facts.aio_agent_version"]["node"]] =+ 1
end
}
puts hash2
output:
{"windows"=>"node", "CentOS"=>"node"}

json = File.read('./data.json')
array = JSON.parse(json)
result = {}
array.each do |hash|
os = hash['facts.operatingsystem']
version = hash['facts.aio_agent_version']
count = result.dig(os, version, 'nodes')
if count
result[os][version]['nodes'] = count + 1
else
result[os] = { version => { 'nodes' => 1 } }.merge(result[os] || {})
end
end
puts result
for every hash in the array, we dig into our results hash to determine if we previously saw a node. if we did, we increment the count. if we didn't we create the node in the results hash. since an os can have many versions, we merge the existing nodes with the newly created node.

Related

Group array of hashes by value and retain structure (hash) in Ruby

I have a hash like this:
hash = {
'en-us': {
where: 'USA'
},
'en-us-zone2': {
where: 'USA'
},
'en-nl': {
where: 'Netherlands'
},
'en-pt': {
where: 'Portugal'
},
}
And I tried to group them using group_by:
result = hash.group_by { |k,v| v[:where] }
However, It returns full of array not by array of hashes. So here is expected and actual results:
Actual Result:
{ "USA"=>
[[:"en-us", {:where=>"USA"}], [:"en-us-zone2", {:where=>"USA"}]],
"Netherlands"=>
[[:"en-nl", {:where=>"Netherlands"}]],
"Portugal"=>
[[:"en-pt", {:where=>"Portugal"}]]
}
Expected Result:
{ "USA"=>
[{:"en-us" => {:where=>"USA"}}, {:"en-us-zone2" => {:where=>"USA"}}]
"Netherlands"=>
[{:"en-nl" => {:where=>"Netherlands"}}]
"Portugal"=>
[{:"en-pt" => {:where=>"Portugal"}}]
}
See, Actual is Array of arrays, instead of array of hashes. Hash keys becomes first array element.
How can I group my hash based on :where ?
This should work:
hash.group_by { |k,v| v[:where] }.each { |_, v| v.map! { |array| { array[0] => array[1] } } }
Or with transform_values
hash.group_by { |k,v| v[:where] }.transform_values { |v| v.map { |array| {array[0] => array[1] } } }
https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values
It's ugly but works:
hash = {
'en-us': {
where: 'USA'
},
'en-us-zone2': {
where: 'USA'
},
'en-nl': {
where: 'Netherlands'
},
'en-pt': {
where: 'Portugal'
},
}
hash.
group_by { |_, v| v[:where] }.
map do |k, v|
[
k,
v.map { |a, b| {a => b} }
]
end.to_h
# => {"USA"=>[{:"en-us"=>{:where=>"USA"}}, {:"en-us-zone2"=>{:where=>"USA"}}], "Netherlands"=>[{:"en-nl"=>{:where=>"Netherlands"}}], "Portugal"=>[{:"en-pt"=>{:where=>"Portugal"}}]}

Populating a hash from an array

I have this array:
params[:types] = [type1, type2, type3...]
I would like to populate my hash the following way using the above array:
params[:hash] = {
"type1" => {
something: something
},
"type2" => {
something: something
},
}
Using a for loop like for index in i ...params[:types] just populates the hash with the last value in the array.
You can use the each_with_object method to do this:
params = {}
params[:types] = ["type1", "type2", "type3"]
params[:types].each_with_object({}) { |k, h| h[k] = { "something" => "something" } }
That last line will return:
=> {"type1"=>{"something"=>"something"}, "type2"=>{"something"=>"something"}, "type3"=>{"something"=>"something"}}
Here is a code snippet example that does what you need.
hash = {}
array.each do |a|
hash[a.to_s] = { "something" => "something" }
end
output:
hash
=> {
"type1" => {
"something" => "something"
},
"type2" => {
"something" => "something"
},
"type3" => {
"something" => "something"
}
}
You could do this:
params = { types: ["type1", "type2", "type3"] }
Hash[params[:types].product([{"something" => "something"}])]
#=> {"type1"=>{"something"=>"something"},
# "type2"=>{"something"=>"something"},
# "type3"=>{"something"=>"something"}}
or with Ruby 2.1,
params[:types].product([{"something" => "something"}]).to_h
If you want a different hash for each element of params[:types]:
hashes = [{ "something1"=>"something1" }, { "something2"=>"something2" },
{ "something3"=>"something3" }]
then
Hash[params[:types].zip(hashes)]
#=> {"type1"=>{"something1"=>"something1"},
# "type2"=>{"something2"=>"something2"},
# "type3"=>{"something3"=>"something3"}}

Split an array of hashes in two based on the value of a key

How can split this array of hashes in two based on the value of the ate key?
array = [
{ name: "Gad", ate: true },
{ name: "Lad", ate: false },
{ name: "Bad", ate: true },
{ name: "Sad", ate: false }
]
Example output
array_1 = [
{ name: "Gad", ate: true },
{ name: "Bad", ate: true }
]
array_2 = [
{ name: "Lad", ate: false },
{ name: "Sad", ate: false }
]
Use the Enumerable#partition method:
array.partition { |x| x[:ate] }
# => [[{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}],
# [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]]
Or:
array_1, array_2 = array.partition { |x| x[:ate] }
array_1
# => [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
array_2
# => [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
array_one, array_two = *array.group_by { |x| x[:ate] }.map(&:last)
=> array_one
=> # [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
=> array_two
=> # [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
thx #CarySwoveland
I can't compete with partition, but here's another way:
trues = array.select { |h| h[:ate] }
falses = array - trues

hash deep_merge with arrays

I've searched through all the answers regarding the use of deep_merge; however, I am still having trouble with my particular issue. I'm trying to merge 2 hashes and add a specific key for each match. For example:
UPDATED THE FORMAT
Hash 1:
{
"actions"=> [
{
"causes"=> [
{
"shortDescription"=>"short description for run 1",
"userId"=>"user.a"
}
]
}
],
"artifacts"=> [],
"fullDisplayName"=>"Run #1",
"result"=>"FAILURE",
"changeSet"=> {
"items"=>[],
"kind"=>nil
},
"culprits"=> []
}
Hash 2:
{
"actions"=> [
{
"causes"=> [
{
"shortDescription"=>"short description for run 2",
"userId"=>"user.b"
}
]
}
],
"artifacts"=> [],
"fullDisplayName"=>"Run #2",
"result"=>"FAILURE",
"changeSet"=> {
"items"=>[],
"kind"=>nil
},
"culprits"=> []
}
Key list:
["key-one","key-two"]
I would like the resulting hash to be:
{
"actions"=> [
{
"causes"=> [
{
"shortDescription"=> {
"key-one" => "short description for run 1",
"key-two" => "short description for run 2"
},
"userId"=> {
"key-one" => "user.a",
"key-two" => "user.b"
}
}
]
}
],
"artifacts"=> {
"key-one" => [],
"key-two" => []
},
"fullDisplayName"=> {
"key-one" => "Run #1",
"key-two" => "Run #2"
},
"result"=> {
"key-one" => "FAILURE",
"key-two" => "FAILURE"
},
"changeSet"=> {
"items"=> {
"key-one" => [], "key-two" => []
},
"kind"=> {
"key-one" => nil,
"key-two" => nil
}
},
"culprits"=> {
"key-one" => [],
"key-two" => []
}
}
If h1 and h2 are the two hashes you wish to merge, and h3 is the desired result, I think the following should work:
#merge_key1, #merge_key2 = "key-one", "key-two"
def merge_em(g1, g2)
case g1
when Array
g1.size.times {|i| merge_em(g1[i], g2[i])}
when Hash
g1.keys.each do |k|
v = g1[k]
if (Hash === v || (Array === v && !v.empty?))
merge_em(v, g2[k])
else
g1[k] = {#merge_key1 => v, #merge_key2 => g2[k]}
end
end
end
end
h = Marshal.load(Marshal.dump(h1))
merge_em(h, h2)
p (h == h3) # => true
A few notes:
This solution depends on the hash structures; it may not work if the structures are changed.
Marshal.load(Marshal.dump(h1)) is used to make a "deep copy" of h1, so that h1 is not modified. If h1 can be modified, merge_em(h1, h2) is sufficient, with h1 being the merged hash.
The gem awesome_print gives you a nicely-formatted display of complex structures like these. All you'd need to do here is require 'ap' followed by ap h. Try it!

How to sort an array by prioritizing specific attributes

So I'm getting records from ActiveRecord and I'd like to do something like:
VIP_LIST = ['John', 'Larry', 'Dan']
records = [
{ name: "Adam" },
{ name: "Larry" },
{ name: "John" },
{ name: "Eric" },
{ name: "Dan" }
]
# This is what I want to end up with:
sort_please(records, VIP_LIST)
=> [
{ name: "John" },
{ name: "Larry" },
{ name: "Dan" },
{ name: "Adam" },
{ name: "Eric" }
]
How can i achieve this?
P.S. There could be values in VIP_LIST that are not even in records
It's not a clever one liner, but it works:
VIP_LIST = ['John', 'Larry', 'Dan', 'Fred']
records = [
{ name: "Adam" },
{ name: "Larry" },
{ name: "John" },
{ name: "Eric" },
{ name: "Dan" }
]
sorted = records.sort_by do |record|
name = record[:name]
if VIP_LIST.include?(name)
VIP_LIST.index(name)
else
records.index(record) + VIP_LIST.length
end
end
p sorted # => [{:name=>"John"}, {:name=>"Larry"}, {:name=>"Dan"}, {:name=>"Adam"}, {:name=>"Eric"}]
try this:
records.sort_by do |x|
[VIP_LIST.index(x[:name]) || VIP_LIST.length, records.index(x)]
end
# => [{:name=>"John"}, {:name=>"Larry"}, {:name=>"Dan"}, {:name=>"Adam"}, {:name=>"Eric"}]
this one?
new_arr = []
VIP_LIST.each do |v|
new_arr << records.select {|r| r[:name] == v } unless nil?
end
new_arr.flatten!
new_arr = new_arr + (records - new_arr)
I think this is both clear and concise:
vip = records.select{ |hash| VIP_LIST.include? hash[:name] }
vip = vip.sort_by{|x| VIP_LIST.find_index(x[:name])} #reorder vip array
sorted_records = (vip + records).uniq

Resources