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

时间:2015-05-11 19:53:36

标签: ruby hash hashmap

我试图比较两个哈希表的结构。我想看看任何给定的哈希表是否是主表的子集。

有效子表的示例:

master_table = {a: String, b: Object, c: { nested_a: Integer, nested_b: Integer} }

my_table     = {a: 'cool value', c: {nested_b: 540}


无效子表的示例

my_table     = {a: WrongType.new}

my_table     = {a: 'cool value', new_key: "I don't belong here!"}

编辑:如果它看起来有点像鸭子和嘎嘎声有点像鸭子,那么我会接受它作为鸭子。

我有一个数据驱动的应用程序,用户必须提供定义应用程序行为的配置文件。我想确保用户的配置文件与主配置文件中定义的结构和类型相匹配。

关于Sergio Tulentsev的评论,上面的问题是在无效子表的第一个例子中:a的类型根据主表无效。在第二种情况下,存在主表中不存在的密钥,因此不正确。

3 个答案:

答案 0 :(得分:2)

<强>代码

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

答案 1 :(得分:1)

好的,我很无聊,所以你走了。猴子修补(从标准库中定义Hash类的方法)是可选的,你的新作业就是摆脱它。

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

答案 2 :(得分:1)

除了比较哈希结构之外,您还做了很多其他事情。您以非常具体的方式(子集)比较结构。您还要检查实际值的有效性。要卸载到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))

检查哈希的有效性只需将其传递给valid?的{​​{1}}方法。

master_table

这些测试的结果是:

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

使用验证类的一个主要优点是您现在可以轻松扩展您的解决方案。例如,添加一个&#34; required&#34;验证选项,如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))