Bharath M
Bharath M

Reputation: 138

I have a array of arrays as in description need to convert it to formatted Hash in Ruby

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

Answers (3)

Cary Swoveland
Cary Swoveland

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

Lars Haugseth
Lars Haugseth

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

Simple Lime
Simple Lime

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

Related Questions