简体   繁体   English

如何递归地对ruby中的哈希进行排序

[英]How to sort hash in ruby recursively

I have a huge hash/json in below format.我有以下格式的巨大哈希/json。 Sample is given.给出了样品。 How we can sort this result hash based on the "total" key in descending order?我们如何根据“total”键以降序对这个结果哈希进行排序?

Nb: The last level nested node will not have have response and total keys.注意:最后一级嵌套节点将没有responsetotal键。 They will have metrics as keys.他们将metrics作为键。

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 } },
        }
      }
    }
  }
}

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 )类似于:(工作示例: 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}}}}}}}

Let's first simplify your example.让我们首先简化您的示例。

result = {
  "account_id_1": {
    "total": 1000,
    "response": {
      "location_1": { "total": 300, "response": 1 },
      "location_2": { "total": 500, "response": 2 },
      "location_3": { "total": 200, "response": 3 }
    }
  },
  "account_id_2": {
    "total": 2000,
    "response": {
      "location_1": { "total": 300,  "response": 1 },
      "location_2": { "total": 500,  "response": 2 },
      "location_3": { "total": 1200, "response": 3 }
    }
  }
}

In your example,在你的例子中,

result[:account_id_1][:response][:location_1][:response]
  #=> { "service_1": { "metrics": { "cost": 100} },
  #     "service_2": { "metrics": { "cost": 100 } },
  #     "service_3": { "metrics": { "cost": 100 } } }

This value, and others like it, will remain unchanged, so I've replaced it with a placeholder, an arbitrary integer:这个值和其他类似的值将保持不变,所以我用占位符替换了它,一个任意整数:

result[:account_id_1][:response][:location_1][:response]
  #=> 1
result[:account_id_1][:response][:location_2][:response]
  #=> 2

and so on.等等。


I found it useful to create a helper method:我发现创建一个辅助方法很有用:

def sort_by_total_desc(h)
  h.sort_by { |_,g| -g[:total] }.to_h
end

The desired hash can then be computed as follows.然后可以如下计算所需的哈希值。

sort_by_total_desc(result.transform_values do |v|
  v.merge(response: sort_by_total_desc(v[:response]))
end)
  #=> {:account_id_2=>{
  #      :total=>2000,
  #      :response=>{
  #        :location_3=>{:total=>1200, :response=>3},
  #        :location_2=>{:total=>500, :response=>2},
  #        :location_1=>{:total=>300, :response=>1}
  #      }
  #    },
  #    :account_id_1=>{
  #      :total=>1000,
  #      :response=>{
  #        :location_2=>{:total=>500, :response=>2},
  #        :location_1=>{:total=>300, :response=>1},
  #        :location_3=>{:total=>200, :response=>3}
  #      }
  #    }
  #   }

Regarding the helper method, here is an example.关于辅助方法,这里有一个例子。

h = { "location_1": { "total": 300, "response": 1 },
      "location_2": { "total": 500, "response": 2 },
      "location_3": { "total": 200, "response": 3 } }
sort_by_total_desc(h)
  #=> {:location_2=>{:total=>500, :response=>2},
  #    :location_1=>{:total=>300, :response=>1},
  #    :location_3=>{:total=>200, :response=>3}}

which requires two steps:这需要两个步骤:

a = h.sort_by { |_,g| -g[:total] }
  #=> [[:location_2, {:total=>500, :response=>2}],
  #    [:location_1, {:total=>300, :response=>1}],
  #    [:location_3, {:total=>200, :response=>3}]]
a.to_h
  #=> as above

The main method has two main steps.主要方法有两个主要步骤。 First perform the "inner" sorting.首先执行“内部”排序。

h = result.transform_values do |v|
  v.merge(response: sort_by_total_desc(v[:response]))
end
  #=> {:account_id_1=>{
  #      :total=>1000,
  #      :response=>{
  #        :location_2=>{:total=>500, :response=>2},
  #        :location_1=>{:total=>300, :response=>1},
  #        :location_3=>{:total=>200, :response=>3}
  #      }
  #    },
  #    :account_id_2=>{
  #      :total=>2000,
  #      :response=>{
  #        :location_3=>{:total=>1200, :response=>3},
  #        :location_2=>{:total=>500, :response=>2},
  #        :location_1=>{:total=>300, :response=>1}
  #      }
  #    }
  #   }

Then do the "outer" sorting to return the desired result:然后进行“外部”排序以返回所需的结果:

sort_by_total_desc(h)
  #=> as above

See Enumerable#sort_by , Hash#transform_values and Hash#merge .请参阅Enumerable#sort_byHash#transform_valuesHash#merge

您可以使用以下代码根据总值进行排序。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM