如何比较两个哈希表的结构和类型,而不是值

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_tablevalid? 方法的问题。

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)).