すべての兄弟キーを使用して Ruby でハッシュを埋めるか完成させる
概要
Rails API を使用して車両をクエリし、さまざまな属性ごとに数を数えてグループ化することができます。 group by を使用する場合、応答にゼロ値を入力したいと考えています。
簡単な例を次に示します。
data = {
AUDI: {
Petrol: 379,
Diesel: 326,
Electric: 447
},
TESLA: {
Electric: 779
}
}
テスラにはガソリン車やディーゼル車がないため、そのキーは応答に含まれていません。これは、結果にゼロカウントが含まれない postgres の group_by の結果だと思います。
これらの欠落したゼロ値でハッシュを「埋める」ことができる関数を作成しようとしています。
fill_hash(data)
返すべきです
data = {
AUDI: {
Petrol: 379,
Diesel: 326,
Electric: 447
},
TESLA: {
Petrol: 0,
Diesel: 0,
Electric: 779
}
}
この単純なケースでは、次の 3 つの方法を使用しました。
def collect_keys(hash, keys = [])
hash.each do |_, value|
if value.is_a?(Hash)
keys.concat(value.keys)
collect_keys(value, keys)
end
end
keys.uniq
end
def fill_missing_keys(hash, all_keys, default_value = 0)
hash.each do |_, value|
if value.is_a?(Hash)
all_keys.each do |k|
value[k] = default_value unless value.key?(k)
end
fill_missing_keys(value, all_keys, default_value)
end
end
hash
end
def fill_hash(hash, default_value = 0)
all_keys = collect_keys(hash)
fill_missing_keys(hash, all_keys, default_value)
end
# Example usage:
hash = { a: { x: 1 }, b: { y: 1 } }
filled_hash = complete_hash(hash)
puts filled_hash
# Output should be: {:a=>{:x=>1, :y=>0}, :b=>{:y=>1, :x=>0}}
私の問題は、ハッシュがより複雑になる可能性があることです。単純なケースでは 2 つの属性でグループ化するだけでしたが、ここでは 3 つの属性でグループ化する例を示します。
{
AUDI: {
Deregistered: {
Diesel: 56
},
Registered: {
Petrol: 379,
Diesel: 270,
Electric: 447
}
},
TESLA: {
Registered: {
Electric: 779
}
}
}
私の希望する出力は次のとおりです。
{
AUDI: {
Deregistered: {
Petrol: 0,
Diesel: 56,
Electric: 0
},
Registered: {
Petrol: 379,
Diesel: 270,
Electric: 447
}
},
TESLA: {
Deregistered: {
Petrol: 0,
Diesel: 0,
Electric: 0
},
Registered: {
Petrol: 0,
Diesel: 0,
Electric: 779
}
}
}
解決策
# This will build a "Default" Hash
# for Example:
# {:AUDI=>{:Petrol=>0, :Diesel=>0, :Electric=>0},
# :TESLA=>{:Petrol=>0, :Diesel=>0, :Electric=>0}}
def build_default_hash(obj, default_value: 0)
return obj.transform_values {default_value} unless obj.values.first.is_a?(Hash)
m = obj.values.reduce(&:deep_merge)
obj.keys.product([build_default_hash(m, default_value:)]).to_h
end
# deep_merge the default hash with the existing Hash
def deep_normalize_hash(h, default_value:0)
build_default_hash(h, default_value:).deep_merge(h)
end
h = {
AUDI: {
Deregistered: {
Diesel: 56
},
Registered: {
Petrol: 379,
Diesel: 270,
Electric: 447
}
},
TESLA: {
Registered: {
Electric: 779
}
}
}
deep_normalize_hash(h)
#=> {:AUDI=>
# {:Deregistered=>{:Diesel=>56, :Petrol=>0, :Electric=>0},
# :Registered=>{:Diesel=>270, :Petrol=>379, :Electric=>447}},
# :TESLA=>
# {:Deregistered=>{:Diesel=>0, :Petrol=>0, :Electric=>0},
# :Registered=>{:Diesel=>0, :Petrol=>0, :Electric=>779}}}
これを Hash クラスに簡単にブレンドして、使用方法を h.deep_normalize にすることもできます。一般的にはそうすることはお勧めしませんが、これが Rails コンテキスト内にあることを考えると、コア クラスの操作がもう少し多くても誰も気付かないでしょう。