Home > Net >  How to sort hash in ruby recursively
How to sort hash in ruby recursively

Time:05-12

I have a huge hash/json in below format. Sample is given. How we can sort this result hash based on the "total" key in descending order?

Nb: The last level nested node will not have have response and total keys. They will have metrics as keys.

result = {
  "account_id_1": {
    "total": 1000,
    "response": {
      "location_1": {
        "total": 300,
        "response": {
          "service_1": { "metrics": { "cost": 100} },
          "service_2": { "metrics": { "cost": 100 } },
          "service_3": { "metrics": { "cost": 100 } },
        }
      },
      "location_2": {
        "total": 500,
        "response": {
          "service_1": { "metrics": { "cost": 300 } },
          "service_2": { "metrics": { "cost": 150 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      },
      "location_3": {
        "total": 200,
        "response": {
          "service_1": { "metrics": { "cost": 75 } },
          "service_2": { "metrics": { "cost": 75 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      }
    }
  },
  "account_id_2": {
    "total": 2000,
    "response": {
      "location_1": {
        "total": 300,
        "response": {
          "service_1": { "metrics": { "cost": 100 } },
          "service_2": { "metrics": { "cost": 100 } },
          "service_3": { "metrics": { "cost": 100 } },
        }
      },
      "location_2": {
        "total": 500,
        "response": {
          "service_1": { "metrics": { "cost": 300 } },
          "service_2": { "metrics": { "cost": 150 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      },
      "location_3": {
        "total": 1200,
        "response": {
          "service_1": { "metrics": { "cost": 1075 } },
          "service_2": { "metrics": { "cost": 75 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      }
    }
  }
}

Expected result:

result = {
  "account_id_2": {
    "total": 2000,
    "response": {
      "location_3": {
        "total": 1200,
        "response": {
          "service_1": { "metrics": { "cost": 1075 } },
          "service_2": { "metrics": { "cost": 75 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      },
      "location_2": {
        "total": 500,
        "response": {
          "service_1": { "metrics": { "cost": 300 } },
          "service_2": { "metrics": { "cost": 150 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      },
      "location_1": {
        "total": 300,
        "response": {
          "service_1": { "metrics": { "cost": 100 } },
          "service_2": { "metrics": { "cost": 100 } },
          "service_3": { "metrics": { "cost": 100 } },
        }
      }
    }
  },
  "account_id_1": {
    "total": 1000,
    "response": {
      "location_2": {
        "total": 500,
        "response": {
          "service_1": { "metrics": { "cost": 300 } },
          "service_2": { "metrics": { "cost": 150 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      },
      "location_1": {
        "total": 300,
        "response": {
          "service_1": { "metrics": { "cost": 100} },
          "service_2": { "metrics": { "cost": 100 } },
          "service_3": { "metrics": { "cost": 100 } },
        }
      },
      "location_3": {
        "total": 200,
        "response": {
          "service_1": { "metrics": { "cost": 75 } },
          "service_2": { "metrics": { "cost": 75 } },
          "service_3": { "metrics": { "cost": 50 } },
        }
      }
    }
  }
}

CodePudding user response:

You can use below code for sorting based on total value.

 result.sort_by { |_key,value| value[:total] }.reverse.to_h

CodePudding user response:

Personally I would create a class to handle the custom sorting so that we can implement our own <=> (Spaceship Operator)

Something like: (Working Example: https://replit.com/@engineersmnky/ThankfulAutomaticRam#main.rb)

class AccountLocaleMetricSorter
  include Comparable
  def self.sort(h)
    h.map do |name,values| 
      new(name: name, data: values)
    end.sort.reduce({}) {|m,o| m.merge(o.to_h)}
  end 

  attr_reader :name, :data, :o_data
  
  def initialize(name: , data: )
    @name = name 
    @o_data = data
    @data = parse_response(data)  
  end 
  
  def to_h
    return {name.to_sym => o_data} unless o_data.key?(:total)
    {name.to_sym => {
        total: o_data[:total],
        response: data.to_h
      }
    }
  end 
  
  def <=>(other)
    if o_data.key?(:total) && o_data.key?(:total)
      other.o_data[:total] <=> o_data[:total]
    elsif other.o_data.key?(:metrics) && o_data.key?(:metrics)
      other.o_data.dig(:metrics,:cost) <=> o_data.dig(:metrics,:cost) 
    else 
      0
    end 
  end

  private 
    def parse_response(response)
      return response unless response.key?(:response)
      self.class.sort(response[:response])
    end 
end 

Usage as:

pp AccountLocaleMetricSorter.sort(result) 

Output:

{:account_id_2=>
  {:total=>2000,
   :response=>
    {:location_3=>
      {:total=>1200,
       :response=>
        {:service_1=>{:metrics=>{:cost=>1075}},
         :service_2=>{:metrics=>{:cost=>75}},
         :service_3=>{:metrics=>{:cost=>50}}}},
     :location_2=>
      {:total=>500,
       :response=>
        {:service_1=>{:metrics=>{:cost=>300}},
         :service_2=>{:metrics=>{:cost=>150}},
         :service_3=>{:metrics=>{:cost=>50}}}},
     :location_1=>
      {:total=>300,
       :response=>
        {:service_1=>{:metrics=>{:cost=>100}},
         :service_2=>{:metrics=>{:cost=>100}},
         :service_3=>{:metrics=>{:cost=>100}}}}}},
 :account_id_1=>
  {:total=>1000,
   :response=>
    {:location_2=>
      {:total=>500,
       :response=>
        {:service_3=>{:metrics=>{:cost=>300}},
         :service_2=>{:metrics=>{:cost=>150}},
         :service_1=>{:metrics=>{:cost=>50}}}},
     :location_1=>
      {:total=>300,
       :response=>
        {:service_1=>{:metrics=>{:cost=>100}},
         :service_2=>{:metrics=>{:cost=>100}},
         :service_3=>{:metrics=>{:cost=>100}}}},
     :location_3=>
      {:total=>200,
       :response=>
        {:service_1=>{:metrics=>{:cost=>75}},
         :service_2=>{:metrics=>{:cost=>75}},
         :service_3=>{:metrics=>{:cost=>50}}}}}}}
  • Related