我有以下简单的Ruby哈希:
{
"builder_rule_0_filter"=>"artist_name",
"builder_rule_0_operator"=>"contains",
"builder_rule_0_value_0"=>"New Found Glory",
"builder_rule_1_filter"=>"bpm",
"builder_rule_1_operator"=>"less",
"builder_rule_1_value_0"=>"150",
"builder_rule_2_filter"=>"days_ago",
"builder_rule_2_operator"=>"less",
"builder_rule_2_value_0"=>"40",
"builder_rule_3_filter"=>"release_date_start",
"builder_rule_3_operator"=>"between",
"builder_rule_3_value_0"=>"2019-01-01",
"builder_rule_3_value_1"=>"2019-12-31",
}
我想将其转换为更有条理/更有用的嵌套哈希:
{
"0"=>
{
"filter"=>"artist_name",
"operator"=>"contains",
"values"=>
{
"0"=>"New Found Glory"
}
},
"1"=>
{
"filter"=>"bpm",
"operator"=>"less",
"values"=>
{
"0"=>"150"
}
}
"2"=>
{
"filter"=>"days_ago",
"operator"=>"less",
"values"=>
{
"0"=>"40"
}
}
"3"=>
{
"filter"=>"release_date_start",
"operator"=>"between",
"values"=>
{
"0"=>"2019-01-01"
"1"=>"2019-12-31"
}
}
}
那么,如何转换一个平面哈希(从表单中获取)并将其转换为基于这些键名的嵌套?
答案 0 :(得分:5)
首先,您可以根据哈希的前缀编号将其拆分为多个哈希(可以随意运行以查看返回值)
groups = input.
group_by { |k,v| k.match(/builder_rule_(\d+)/)[1] }.
transform_values(&:to_h)
在这一点上,如果仅使用一些硬编码来构建键值,则创建内部对象会更容易:
result = groups.each_with_object({}) do |(prefix, hash), memo|
memo[prefix] = {
"filter" => hash["builder_rule_#{prefix}_filter"],
"operator" => hash["builder_rule_#{prefix}_operator"],
"values" => hash.select do |key, val|
key =~ /builder_rule_#{prefix}_value/
end.sort_by { |key, val| key }.map { |(key, val)| val }
}
end
这可能会混淆.sort_by { |key, val| key }.map { |(key, val)| val }
的含义。我可以清楚地说明:
hash.select { |key, val| key =~ /builder_rule_#{prefix}_value/ }
获取将用于“值”数组的键值。它返回一个哈希。.sort_by { |key, val| key }
将哈希值转换为[key, val]
元组的数组,并按键排序。这样可以使值以正确的顺序显示。.map { |(key, val)| val }
将嵌套数组变成单级数组,并丢弃键您也可以使用sort_by(&:first).map(&:second)
,尽管您需要积极的支持才能使用Array#second
答案 1 :(得分:2)
假设深度固定为3,并且规则是一致的(如示例中所示),则可以如下进行一次单次转换:
h.each_with_object(Hash.new {|h,k| h[k]= {}}) do |(k,v),obj|
new_k, k2, k3 = k[/(?<=builder_rule_).*/].split('_')
k2 == 'value' ? (obj[new_k]["values"] ||= {}).merge!({k3 => v}) : obj[new_k][k2] = v
end
这里我们正在构造一个新的Hash
,其中第一级键默认为空的Hash
(h.each_with_object(Hash.new {|h,k| h[k]= {}}) do |(k,v),obj|
)。
然后,我们根据'builder_rule_'new_k, k2, k3 = k[/(?<=builder_rule_).*/].split('_')
然后,如果第二级密钥(k2)等于“值”,则需要将其设置为值,并将第三级密钥(k3)和该值合并到新的哈希中,否则我们将k2分配给该值。 k2 == 'value' ? (obj[new_k]["values"] ||= {}).merge!({k3 => v}) : obj[new_k][k2] = v
这实际上将返回请求的要求,其中values
是具有关键索引的Hash
{
"0"=>{
"filter"=>"artist_name",
"operator"=>"contains",
"values"=>
{
"0"=>"New Found Glory"
}
},
"1"=>{
"filter"=>"bpm",
"operator"=>"less",
"values"=>
{
"0"=>"150"
}
},
"2"=>{
"filter"=>"days_ago",
"operator"=>"less",
"values"=>
{
"0"=>"40"
}
},
"3"=>{
"filter"=>"release_date_start",
"operator"=>"between",
"values"=>
{
"0"=>"2019-01-01",
"1"=>"2019-12-31"
}
}
}
答案 2 :(得分:1)
可接受的答案是好的,但是比严格必要的工作要多得多。我会用each_with_object
完成所有工作:
input.each_with_object({}}) do |(key, val), hsh|
_, num, name = *key.match(/^builder_rule_(\d+)_(.*)/)
hsh[num] = {} unless hsh.key?(num)
if name.start_with?(/value_\d/)
hsh[num]["values"] = [] unless hsh[num].key?("values")
hsh[num]["values"] << val
else
hsh[num][name] = val
end
end
# => {
# "0" => {
# "filter" => "artist_name",
# "operator" => "contains",
# "values" => ["New Found Glory"]
# },
# "1" => {
# "filter" => "bpm",
# "operator" => "less",
# "values" => ["150"]
# },
# "2" => {
# "filter" => "days_ago",
# "operator" => "less",
# "values" => ["40"]
# },
# "3" => {
# "filter" => "release_date_start",
# "operator" => "between",
# "values" => ["2019-01-01", "2019-12-31"]
# }
# }
查看有关repl.it的实际操作:https://repl.it/@jrunning/GiddyEnragedLinks