Reputation: 138
I have a file with many arrays as below:
[["default", "'drop'"],
["rule", "49", "action", "'accept'"],
["rule", "49", "description", "'This one is for'"],
["rule", "49", "destination", "address", "'1.2.3.4/20'"],
["rule", "50", "action", "'accept'"],
["rule", "50", "description", "'Once more'"],
["rule", "50", "destination", "address", "'1.2.3.5/20'"]]
I want a ruby code which can make it look like following:
{
'default': 'drop',
'rule': {
'49': {
'destination': {
'address': '1.2.3.4/20'
},
'action': 'accept',
'description': 'This one is for'
},
'50': {
'destination': {
'address': '1.2.3.5/20'
},
'action': 'accept',
'description': 'Once more'
}
}
}
I tried different approaches which is replacing and keeping only the last one or few keys are missing. Please help me in this.
Upvotes: 0
Views: 85
Reputation: 110675
This is another recursive solution. One advantage of using recursion is that the array can be of arbitrary size.
arr = [
["default", "'drop'"],
["rule", "49", "action", "'accept'"],
["rule", "49", "description", "'This one is for'"],
["rule", "49", "destination", "address", "'1.2.3.4/20'"],
["rule", "50", "action", "'accept'"],
["rule", "50", "description", "'Once more'"],
["rule", "50", "destination", "address", "'1.2.3.5/20'"]
]
def recurse(arr)
arr.group_by(&:first).transform_values do |a|
a.map! { |r| r.drop(1) }
a.size == 1 && a[0].size == 1 ? a[0][0] : recurse(a)
end
end
recurse arr
#=> { "default"=>"'drop'",
# "rule"=>{
# "49"=>{
# "action"=>"'accept'",
# "description"=>"'This one is for'",
# "destination"=>{"address"=>"'1.2.3.4/20'"}
# },
# "50"=>{
# "action"=>"'accept'",
# "description"=>"'Once more'",
# "destination"=>{"address"=>"'1.2.3.5/20'"}
# }
# }
# }
Note that the first step is as follows.
arr.group_by(&:first)
#=> {"default"=>[["default", "'drop'"]],
# "rule"=>[["rule", "49", "action", "'accept'"],
# ["rule", "49", "description", "'This one is for'"],
# ["rule", "49", "destination", "address", "'1.2.3.4/20'"],
# ["rule", "50", "action", "'accept'"],
# ["rule", "50", "description", "'Once more'"],
# ["rule", "50", "destination", "address", "'1.2.3.5/20'"]]}
Upvotes: 4
Reputation: 14881
class Hash
def deep_store(keys, value)
if keys.size > 1
self[keys.first] ||= {}
self[keys.first].deep_store keys[1..-1], value
else
self[keys.first] = value
end
self
end
end
input = [
["default", "'drop'"],
["rule", "49", "action", "'accept'"],
["rule", "49", "description", "'This one is for'"],
["rule", "49", "destination", "address", "'1.2.3.4/20'"],
["rule", "50", "action", "'accept'"],
["rule", "50", "description", "'Once more'"],
["rule", "50", "destination", "address", "'1.2.3.5/20'"]
]
result = input.each_with_object({}) do |(*keys, value), hash|
hash.deep_store keys, value
end
puts result
# => {"default"=>"'drop'", "rule"=>{"49"=>{"action"=>"'accept'", "description"=>"'This one is for'", "destination"=>{"address"=>"'1.2.3.4/20'"}}, "50"=>{"action"=>"'accept'", "description"=>"'Once more'", "destination"=>{"address"=>"'1.2.3.5/20'"}}}}
Upvotes: 2
Reputation: 11035
This makes use of passing a block to Hash::new
which keeps passing that same block to new sub hashes, via the default_proc
:
output = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
Now, we can just iterate the array, and we know the last value in each subarray becomes the final value (ie, not sub hashes off of it) and everything else needs to create a new sub-hash, which is done above, when we access a key without a value, so we can access all intermediate keys by use of Hash#dig
, we also need to convert all the keys into symbols, and (it appears) remove single quotes from the beginning and end of the values:
array.each_with_object(output) do |(*nesting, key, value), hash|
hash = nesting.empty? ? hash : hash.dig(*nesting.map(&:to_sym))
hash[key.to_sym] = value.gsub(/(\A\'|\'\z)/, '')
end
Running that with array
set to your input array we get the following out:
{
:default => "drop",
:rule => {
:"49" => {
:action => "accept",
:description => "This one is for",
:destination => {
:address => "1.2.3.4/20"
}
},
:"50" => {
:action => "accept",
:description => "Once more",
:destination => {
:address => "1.2.3.5/20"
}
}
}
}
Upvotes: 2