如何展平哈希,使每个键成为唯一值?
Posted
技术标签:
【中文标题】如何展平哈希,使每个键成为唯一值?【英文标题】:How to flatten a hash, making each key a unique value? 【发布时间】:2018-07-27 22:26:14 【问题描述】:我想使用嵌套散列和数组获取散列,并将其展平为具有唯一值的单个散列。我一直试图从不同的角度来解决这个问题,但后来我让它变得比它需要的复杂得多,让自己迷失在正在发生的事情中。
示例源哈希:
"Name" => "Kim Kones",
"License Number" => "54321",
"Details" =>
"Name" => "Kones, Kim",
"Licenses" => [
"License Type" => "PT",
"License Number" => "54321"
,
"License Type" => "Temp",
"License Number" => "T123"
,
"License Type" => "AP",
"License Number" => "A666",
"Expiration Date" => "12/31/2020"
]
所需的哈希示例:
"Name" => "Kim Kones",
"License Number" => "54321",
"Details_Name" => "Kones, Kim",
"Details_Licenses_1_License Type" => "PT",
"Details_Licenses_1_License Number" => "54321",
"Details_Licenses_2_License Type" => "Temp",
"Details_Licenses_2_License Number" => "T123",
"Details_Licenses_3_License Type" => "AP",
"Details_Licenses_3_License Number" => "A666",
"Details_Licenses_3_Expiration Date" => "12/31/2020"
不管怎样,这是我放弃前最近的一次尝试。
def flattify(hashy)
temp =
hashy.each do |key, val|
if val.is_a? String
temp["#key"] = val
elsif val.is_a? Hash
temp.merge(rename val, key, "")
elsif val.is_a? Array
temp["#key"] = enumerate val, key
else
end
print "=> #temp\n"
end
return temp
end
def rename (hashy, str, n)
temp =
hashy.each do |key, val|
if val.is_a? String
temp["#key#n"] = val
elsif val.is_a? Hash
val.each do |k, v|
temp["#key_#k#n"] = v
end
elsif val.is_a? Array
temp["#key"] = enumerate val, key
else
end
end
return flattify temp
end
def enumerate (ary, str)
temp =
i = 1
ary.each do |x|
temp["#str#i"] = x
i += 1
end
return flattify temp
end
【问题讨论】:
你以前有没有看过深度优先搜索? 如果您将变量分配给“示例源哈希”(例如,h = "Name" => "Kim Kones", ...
),将会很有帮助。这样用户就可以在答案和 cmets 中引用变量 (h
) 而无需定义它。
【参考方案1】:
有趣的问题!
理论
这是解析数据的递归方法。
它会跟踪它找到的键和索引。 它将它们附加到tmp
数组中。
找到叶对象后,会将其写入哈希值作为值,并以连接的tmp
作为键。
然后这个小散列被递归地合并回主散列。
代码
def recursive_parsing(object, tmp = [])
case object
when Array
object.each.with_index(1).with_object() do |(element, i), result|
result.merge! recursive_parsing(element, tmp + [i])
end
when Hash
object.each_with_object() do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key])
end
else
tmp.join('_') => object
end
end
举个例子:
require 'pp'
pp recursive_parsing(data)
# "Name"=>"Kim Kones",
# "License Number"=>"54321",
# "Details_Name"=>"Kones, Kim",
# "Details_Licenses_1_License Type"=>"PT",
# "Details_Licenses_1_License Number"=>"54321",
# "Details_Licenses_2_License Type"=>"Temp",
# "Details_Licenses_2_License Number"=>"T123",
# "Details_Licenses_3_License Type"=>"AP",
# "Details_Licenses_3_License Number"=>"A666",
# "Details_Licenses_3_Expiration Date"=>"12/31/2020"
调试
这是一个带有老式调试的修改版本。它可能会帮助您了解发生了什么:
def recursive_parsing(object, tmp = [], indent="")
puts "#indentParsing #object.inspect, with tmp=#tmp.inspect"
result = case object
when Array
puts "#indent It's an array! Let's parse every element:"
object.each_with_object().with_index(1) do |(element, result), i|
result.merge! recursive_parsing(element, tmp + [i], indent + " ")
end
when Hash
puts "#indent It's a hash! Let's parse every key,value pair:"
object.each_with_object() do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key], indent + " ")
end
else
puts "#indent It's a leaf! Let's return a hash"
tmp.join('_') => object
end
puts "#indent Returning #result.inspect\n"
result
end
当使用recursive_parsing([a: 'foo', b: 'bar', c: 'baz'])
调用时,它会显示:
Parsing [:a=>"foo", :b=>"bar", :c=>"baz"], with tmp=[]
It's an array! Let's parse every element:
Parsing :a=>"foo", :b=>"bar", with tmp=[1]
It's a hash! Let's parse every key,value pair:
Parsing "foo", with tmp=[1, :a]
It's a leaf! Let's return a hash
Returning "1_a"=>"foo"
Parsing "bar", with tmp=[1, :b]
It's a leaf! Let's return a hash
Returning "1_b"=>"bar"
Returning "1_a"=>"foo", "1_b"=>"bar"
Parsing :c=>"baz", with tmp=[2]
It's a hash! Let's parse every key,value pair:
Parsing "baz", with tmp=[2, :c]
It's a leaf! Let's return a hash
Returning "2_c"=>"baz"
Returning "2_c"=>"baz"
Returning "1_a"=>"foo", "1_b"=>"bar", "2_c"=>"baz"
【讨论】:
不错,埃里克!我倾向于在开头删除result =
,最后删除result
,将object.each_with_index do |element, i|
更改为object.each_with_object().with_index do |(element, result), i|
,将object.each do |key, value|
更改为object.each_with_object() do |(key, value), result|
并从result = tmp.join('_') => object
中删除result =
,但这只是风格偏好。
@CarySwoveland:谢谢!很高兴知道您仍在传播each_with_object
的爱;)。
用|(element, result), i|
遍历数组,但|(key, value), result|
的哈希让我觉得有些不一致。用object.each.with_index(1).with_object() do |(element, i), result|
来遍历数组怎么样?
添加调试代码是一个不错的选择,这在答案中很少见。创建变量result
的唯一原因是case
语句之后的puts
。另一个教育机会是放弃result
并将tap
链接到case
语句,最后的puts
在其块中。毕竟,tap
的使用是我们拥有该方法的主要原因之一。【参考方案2】:
与其他人不同,我不喜欢each_with_object
:-)。但我确实喜欢传递单个结果哈希,因此我不必一遍又一遍地合并和重新合并哈希。
def flattify(value, result = , path = [])
case value
when Array
value.each.with_index(1) do |v, i|
flattify(v, result, path + [i])
end
when Hash
value.each do |k, v|
flattify(v, result, path + [k])
end
else
result[path.join("_")] = value
end
result
end
(一些细节取自 Eric,见 cmets)
【讨论】:
只是好奇:你是修改了我的代码还是从头开始?看看是否有其他方法可以解决这个问题会很有趣。 @EricDuminil 从头开始做,然后阅读你的并采用了一些细节(case
而不是if
,(1)
而不是+ 1
,并且路径更新效率较低,因为我没有不想看起来比较大:-)。我认为我的第一个工作版本是:eval.in/957963。所以并没有什么不同。对于“其他方式”,我猜你的意思是不像我们的那样递归?【参考方案3】:
非递归方法,将 BFS 与数组一起用作队列。我保留值不是数组/哈希的键值对,并将数组/哈希内容推送到队列(使用组合键)。将数组转换为哈希 (["a", "b"]
↦ 1=>"a", 2=>"b"
) 感觉很整洁。
def flattify(hash)
(q = hash.to_a).select |key, value|
value = (1..value.size).zip(value).to_h if value.is_a? Array
!value.is_a?(Hash) || !value.each |k, v| q << ["#key_#k", v]
.to_h
end
我喜欢它的一件事是"#key_#k"
这样的按键组合很好。在我的其他解决方案中,我也可以使用字符串 path = ''
并使用 path + "_" + k
对其进行扩展,但这会导致前导下划线,我必须避免或使用额外的代码进行修剪。
【讨论】:
它比您的其他答案可读性差得多,但它仍然是一个非常有趣的答案。感谢您寻找替代方案! @EricDuminil 是的,一旦我决定滥用这样的数组(我什至不确定在迭代期间追加是否安全......是吗?),我不再关心清洁:-P以上是关于如何展平哈希,使每个键成为唯一值?的主要内容,如果未能解决你的问题,请参考以下文章
已更新或删除的行值要么不能使该行成为唯一行,要么改变了多个行,如何解决
sql server"已更新或删除的行值要么不能使该行成为唯一行,要么改变了多个行" 解决方案