2015-05-11 46 views
-1

我想比較兩個散列表的結構。我想看看是否有任何給定的散列表是主表的一個子集。有效的子表的如何比較兩個散列表的結構和類型,而不是值

例:無效的分表

my_table  = {a: WrongType.new} 

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

編輯的

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

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


例如:如果它看起來有點像鴨子,庸醫有點像鴨子,那麼我會接受它作爲一隻鴨子。

我有一個數據驅動的應用程序,用戶必須提供一個配置文件來定義應用程序的行爲。我想確保用戶的配置文件與主配置文件中定義的結構和類型相匹配。

關於Sergio Tulentsev的評論,上面的問題是,在無效子表的第一個示例中:a的類型根據主表無效。在第二種情況下,存在主表中不存在的密鑰,因此不正確。

+1

那麼,這裏有什麼問題? –

+0

你究竟想要完成什麼?如果這不僅僅是一個編碼練習,我會重新評估'master_table'是否最好用散列表示,或者更適合作爲知道如何驗證散列的實際對象('my_table')。 –

回答

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 
+0

我真的很喜歡你的答案的簡單!謝謝。 –

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 
+0

看起來不錯,塞爾吉奧。它與我的答案中的其他示例一起工作。 –

+1

我可能說得太快了。 '{a:'cat',c:42} .structural_subset_of?(master)#=> TypeError:需要類或模塊。輕鬆修復。回覆不需要;我會刪除這條評論... –

+0

謝謝你。我們必須離開_something_讓他做,不是嗎? :) –

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?方法的事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對象 - 這是一個比您想要解決的問題更簡單的問題。

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

使用驗證類的一個主要優點是您現在可以輕鬆地擴展您的解決方案。例如,在驗證中添加一個「必需的」選項會很簡單,如HashValidation.new(id: KlassValidation.new(Integer, required: true))