如何比较两个哈希表的结构和类型,而不是值
How to compare the structure and type of two hash tables, not the values
我正在尝试比较两个散列 table 的结构。我想看看任何给定的散列 table 是否是主 table 的子集。
有效子示例table:
master_table = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }
my_table = {a: 'cool value', c: {nested_b: 540}
无效子示例table
my_table = {a: WrongType.new}
或
my_table = {a: 'cool value', new_key: "I don't belong here!"}
编辑:如果它长得有点像鸭子,叫起来也有点像鸭子,那我就接受它是鸭子。
我有一个数据驱动的应用程序,用户必须提供一个定义应用程序行为的配置文件。我想确保用户的配置文件与主配置文件中定义的结构和类型相匹配。
关于 Sergio Tulentsev 的评论,上面的问题是在无效子 table 的第一个示例中,:a 的类型根据主 table 无效。在第二种情况下,存在一个不存在于主 table 中的密钥,因此不正确。
好吧,我很无聊,所以给你。 Monkey-patching(在标准库中 Hash
class 上定义方法)是可选的,你的新作业将是摆脱它。
class Hash
def structural_subset_of?(master)
each_pair do |key, value|
expected_type = master[key]
return false unless expected_type
if value.is_a?(Hash)
return false unless value.structural_subset_of?(master[key])
else
return false unless value.is_a?(expected_type)
end
end
true
end
end
master = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }
valid = {a: 'cool value', c: {nested_b: 540} }
invalid1 = {a: Object.new}
invalid2 = {a: 'cool value', new_key: "I don't belong here!"}
valid.structural_subset_of?(master) # => true
invalid1.structural_subset_of?(master) # => false
invalid2.structural_subset_of?(master) # => false
代码
def valid?(master_table, my_table)
my_table.all? do |k,v|
case master_table.key?(k)
when true
mv = master_table[k]
case v
when Hash then mv.is_a?(Hash) && valid?(mv, v)
else mv.is_a?(Class) && v.is_a?(mv)
end
else false
end
end
end
例子
master_table = {a: String, b: Object, c: {nested_a: Integer, nested_b: Integer}}
my_table = {a: 'cool value', c: {nested_b: 540}}
valid?(master_table, my_table)
#=> true
my_table = {a: 'cool value', new_key: "I don't belong here!"}
valid?(master_table, my_table)
#=> false
my_table = {a: 'cool value', new_key: "I don't belong here!"}
valid?(master_table, my_table)
#=> false
my_table = {a: 'cool value', c: 42}
valid?(master_table, my_table)
#=> false
master_table = {a: String, b: Object, c: {nested_a: {nested_b:
{nested_c: Integer}}}}
my_table = {a: 'cool value', b: [1,2,3], c: {nested_a: {nested_b:
{nested_c: 42}}}}
valid?(master_table, my_table)
#=> true
my_table = {a: 'cool value', b: [1,2,3], c: {nested_a: {nested_b:
{nested_c: 'cat'}}}}
valid?(master_table, my_table)
#=> false
my_table = {a: 'cool value', b: [1,2,3], c: {nested_a: {nested_b: 42}}}
valid?(master_table, my_table)
#=> false
您所做的不仅仅是比较哈希结构。您正在以一种非常具体的方式(子集)比较结构。您还要检查实际值的有效性。要卸载到 Hash
的工作量很大。如果您对 master_table
有任何影响,我会提取一些实际对象来为您完成工作。
这是一个对象的示例解决方案。
我首先创建了一些验证对象:
class KlassValidation
attr_reader :klass
def initialize(klass)
@klass = klass
end
def valid?(hash, key)
return true unless hash.keys.include?(key)
hash[key].is_a? klass
end
end
string_validation = KlassValidation.new(String)
object_validation = KlassValidation.new(Object)
integer_validation = KlassValidation.new(Integer)
class HashValidation
attr_reader :validations
def initialize(validations)
@validations = validations
end
def valid?(hash, key=nil)
hash_to_validate = key ? hash[key] : hash
return true unless hash_to_validate
return false if invalid_keys?(hash_to_validate)
validations.all? { |key, validation| validation.valid?(hash_to_validate, key) }
end
def invalid_keys?(hash)
(hash.keys - validations.keys).any?
end
end
然后 master_table
使用这些对象:
master_table = HashValidation.new(a: string_validation, b: object_validation, c: HashValidation.new(nested_a: integer_validation, nested_b: integer_validation))
然后检查散列的有效性只是将它传递给 master_table
的 valid?
方法的问题。
test_cases = [
{ valid: true, value: {a: 'cool value' } },
{ valid: false, value: {bogus: 'cool value' } },
{ valid: false, value: {a: :symbol } },
{ valid: true, value: {a: 'cool value', c: {nested_b: 540} } },
{ valid: false, value: {a: 'cool value', c: {nested_b: :symbol} } },
{ valid: false, value: {a: 'cool value', c: {bogus: :symbol} } }
]
test_cases.each do |test_case|
if test_case[:valid] == master_table.valid?(test_case[:value])
puts "Good!"
else
puts ">>>#{test_case[:value]} was not #{test_case[:valid]}"
end
end
这些测试的结果是:
Good!
Good!
Good!
Good!
Good!
Good!
现在,如果您 必须 在您的问题中以 my_table
形式的散列开头,我仍然会使用 HashValidation
执行验证。在这种情况下,您的问题是将 my_table
转换为 HashValidation
对象 - 一个比您要解决的问题简单得多的问题。
master_table_orig = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }
def create_hash_validation(hash)
hash.inject({}) do |acc, (key, value)|
acc[key] = if value.is_a?(Hash)
HashValidation.new(create_hash_validation(hash[key]))
else
KlassValidation.new(value)
end
acc
end
end
master_table = HashValidation.new(create_hash_validation(master_table_orig))
使用验证 类 的一个主要优点是您现在可以轻松地扩展您的解决方案。例如,将 "required" 选项添加到验证中会很简单,如 HashValidation.new(id: KlassValidation.new(Integer, required: true))
.
我正在尝试比较两个散列 table 的结构。我想看看任何给定的散列 table 是否是主 table 的子集。
有效子示例table:
master_table = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }
my_table = {a: 'cool value', c: {nested_b: 540}
无效子示例table
my_table = {a: WrongType.new}
或
my_table = {a: 'cool value', new_key: "I don't belong here!"}
编辑:如果它长得有点像鸭子,叫起来也有点像鸭子,那我就接受它是鸭子。
我有一个数据驱动的应用程序,用户必须提供一个定义应用程序行为的配置文件。我想确保用户的配置文件与主配置文件中定义的结构和类型相匹配。
关于 Sergio Tulentsev 的评论,上面的问题是在无效子 table 的第一个示例中,:a 的类型根据主 table 无效。在第二种情况下,存在一个不存在于主 table 中的密钥,因此不正确。
好吧,我很无聊,所以给你。 Monkey-patching(在标准库中 Hash
class 上定义方法)是可选的,你的新作业将是摆脱它。
class Hash
def structural_subset_of?(master)
each_pair do |key, value|
expected_type = master[key]
return false unless expected_type
if value.is_a?(Hash)
return false unless value.structural_subset_of?(master[key])
else
return false unless value.is_a?(expected_type)
end
end
true
end
end
master = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }
valid = {a: 'cool value', c: {nested_b: 540} }
invalid1 = {a: Object.new}
invalid2 = {a: 'cool value', new_key: "I don't belong here!"}
valid.structural_subset_of?(master) # => true
invalid1.structural_subset_of?(master) # => false
invalid2.structural_subset_of?(master) # => false
代码
def valid?(master_table, my_table)
my_table.all? do |k,v|
case master_table.key?(k)
when true
mv = master_table[k]
case v
when Hash then mv.is_a?(Hash) && valid?(mv, v)
else mv.is_a?(Class) && v.is_a?(mv)
end
else false
end
end
end
例子
master_table = {a: String, b: Object, c: {nested_a: Integer, nested_b: Integer}}
my_table = {a: 'cool value', c: {nested_b: 540}}
valid?(master_table, my_table)
#=> true
my_table = {a: 'cool value', new_key: "I don't belong here!"}
valid?(master_table, my_table)
#=> false
my_table = {a: 'cool value', new_key: "I don't belong here!"}
valid?(master_table, my_table)
#=> false
my_table = {a: 'cool value', c: 42}
valid?(master_table, my_table)
#=> false
master_table = {a: String, b: Object, c: {nested_a: {nested_b:
{nested_c: Integer}}}}
my_table = {a: 'cool value', b: [1,2,3], c: {nested_a: {nested_b:
{nested_c: 42}}}}
valid?(master_table, my_table)
#=> true
my_table = {a: 'cool value', b: [1,2,3], c: {nested_a: {nested_b:
{nested_c: 'cat'}}}}
valid?(master_table, my_table)
#=> false
my_table = {a: 'cool value', b: [1,2,3], c: {nested_a: {nested_b: 42}}}
valid?(master_table, my_table)
#=> false
您所做的不仅仅是比较哈希结构。您正在以一种非常具体的方式(子集)比较结构。您还要检查实际值的有效性。要卸载到 Hash
的工作量很大。如果您对 master_table
有任何影响,我会提取一些实际对象来为您完成工作。
这是一个对象的示例解决方案。
我首先创建了一些验证对象:
class KlassValidation
attr_reader :klass
def initialize(klass)
@klass = klass
end
def valid?(hash, key)
return true unless hash.keys.include?(key)
hash[key].is_a? klass
end
end
string_validation = KlassValidation.new(String)
object_validation = KlassValidation.new(Object)
integer_validation = KlassValidation.new(Integer)
class HashValidation
attr_reader :validations
def initialize(validations)
@validations = validations
end
def valid?(hash, key=nil)
hash_to_validate = key ? hash[key] : hash
return true unless hash_to_validate
return false if invalid_keys?(hash_to_validate)
validations.all? { |key, validation| validation.valid?(hash_to_validate, key) }
end
def invalid_keys?(hash)
(hash.keys - validations.keys).any?
end
end
然后 master_table
使用这些对象:
master_table = HashValidation.new(a: string_validation, b: object_validation, c: HashValidation.new(nested_a: integer_validation, nested_b: integer_validation))
然后检查散列的有效性只是将它传递给 master_table
的 valid?
方法的问题。
test_cases = [
{ valid: true, value: {a: 'cool value' } },
{ valid: false, value: {bogus: 'cool value' } },
{ valid: false, value: {a: :symbol } },
{ valid: true, value: {a: 'cool value', c: {nested_b: 540} } },
{ valid: false, value: {a: 'cool value', c: {nested_b: :symbol} } },
{ valid: false, value: {a: 'cool value', c: {bogus: :symbol} } }
]
test_cases.each do |test_case|
if test_case[:valid] == master_table.valid?(test_case[:value])
puts "Good!"
else
puts ">>>#{test_case[:value]} was not #{test_case[:valid]}"
end
end
这些测试的结果是:
Good!
Good!
Good!
Good!
Good!
Good!
现在,如果您 必须 在您的问题中以 my_table
形式的散列开头,我仍然会使用 HashValidation
执行验证。在这种情况下,您的问题是将 my_table
转换为 HashValidation
对象 - 一个比您要解决的问题简单得多的问题。
master_table_orig = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }
def create_hash_validation(hash)
hash.inject({}) do |acc, (key, value)|
acc[key] = if value.is_a?(Hash)
HashValidation.new(create_hash_validation(hash[key]))
else
KlassValidation.new(value)
end
acc
end
end
master_table = HashValidation.new(create_hash_validation(master_table_orig))
使用验证 类 的一个主要优点是您现在可以轻松地扩展您的解决方案。例如,将 "required" 选项添加到验证中会很简单,如 HashValidation.new(id: KlassValidation.new(Integer, required: true))
.