ruby中的递归Hash变换函数

ruby中的递归Hash变换函数,ruby,recursion,hashmap,iteration,Ruby,Recursion,Hashmap,Iteration,我对响应模式有以下swagger(openAPI)定义: h = { "type"=>"object", "properties"=>{ "books"=>{ "type"=>"array", "items"=>{ "type"=>"object", "properties"=>{ "urn" =>{ "

我对响应模式有以下swagger(openAPI)定义:

h = { "type"=>"object",
      "properties"=>{
        "books"=>{
          "type"=>"array",
          "items"=>{
            "type"=>"object",
            "properties"=>{
              "urn"  =>{ "type"=>"string" },
              "title"=>{ "type"=>"string" }
            }
          }
        }
      }
    }
并希望将其转换为以下格式,以便能够将此响应显示为树:

{ "name"=>"200",
  "children"=> [
    {
      "name"=>"books (array)",
      "children"=> [
        {"name"=>"urn (string)" },
        {"name"=>"title (string)" }
      ]
    }
  ]
}
在swagger模式格式中,节点既可以是对象(具有属性),也可以是项目数组,它们本身就是对象。下面是我编写的函数:schema参数是上面所示的swagger格式的散列,树变量包含
{name:“200”}

我一定是在递归过程中遗漏了一个步骤,或者是以错误的方式传递了树,但我恐怕没有足够的脑力来解决这个问题:)也许一个善良的灵魂会帮我写漂亮的ruby代码

递归!试试长生不老药:-)

为了跟踪递归方法,我编写了大量的
put
,并添加了一个级别号

因为我没有Rails,所以我删除了Rails的东西。稍加修改后,您的输入(书籍数组不是数组!)和代码:

schema = 
    { "type"=>"object",
      "properties"=>{
        "books"=>{
          "type"=>"array",
          "items"=>{
            "type"=>"object",
            "properties"=>{
              "urn"  =>{ "type"=>"string" },
              "title"=>{ "type"=>"string" }
            }
          }
        }
      }
    }

tree = {}

def build_tree(schema, tree, level)
    puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
    case schema['type']
    when 'object'
        puts "in when object for #{schema['properties'].size} properties :"
        i = 0
        schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
        tree[:children] = []
        schema['properties'].each do | property_name, property_schema |
            puts "level=#{level} property_name=#{property_name}"
            tree[:children] << { name: property_name, children: build_tree(property_schema, tree, level + 1) }
        end
    when 'array'
        puts "in when array for #{schema['items'].size} items will process following items :"
        i = 0
        schema['items'].each_key{ | name | puts "#{i+=1}. #{name}" }
        schema['items'].each do | property_name, property_schema |
            puts "level=#{level} property_name=#{property_name}, property_schema=#{property_schema.inspect}"
            tree[:children] << { name: property_name, children: build_tree(property_schema, tree, level + 1) }
        end
    when nil
        puts "in when nil"
        tree[:name] == schema
    end
end

build_tree(schema, tree, 1)
puts tree
(注意:我已经手工打印了结果树)

跟踪显示发生了什么:在
中,当“array”时
当您编写
模式['items']时。每个
您可能需要迭代多个项目。但是没有条目,只有一个散列。因此,
schema['items')。每个
都会在键上迭代。然后使用没有
'type'
键的模式递归,因此
case模式['type']
在为nil时落入

请注意,如果递归调用“object”
而不是在nil
时调用
树[:children]=[]
将删除以前的结果,因为您始终使用相同的初始
树。要堆叠中间结果,需要在递归调用中提供新变量

理解递归的最佳方法不是循环到方法的开头,而是想象一系列调用:

method_1
   |
   +------> method_2
               |
               +------> method_3
如果将与参数相同的初始参数传递给递归调用,它将被最后一个返回值擦除。但如果您传递一个新变量,则可以在累加操作中使用它

如果您检查了
schema['items']
是否真的是一个数组,就像我在解决方案中所做的那样,您会发现输入与预期不符:

$ ruby -w t.rb 
level=1 schema[:type]="object", schema class is Hash
in when object for 1 properties :
1. books
level=1 property_name=books
level=2 schema[:type]="array", schema class is Hash
in when array
oops ! Array expected
{:children=>[{:name=>"books", :children=>"oops ! Array expected"}]}
现在是我的解决方案。我把化妆品的细节留给你

schema = 
    { "type"=>"object",
      "properties"=>{
        "books"=>{
          "type"=>"array",
          "items"=> [ # <----- added [
            { "type"=>"object",
              "properties" => {
                "urn"   => { "type"=>"string" },
                "title" => { "type"=>"string" }
                              }
            },
            { "type"=>"object",
              "properties" => {
                "urn2"   => { "type"=>"string" },
                "title2" => { "type"=>"string" }
                              }
            }
                    ] # <----- added ]
        } # end books
      } # end properties
    } # end schema

tree = {"name"=>"200", children: []}

def build_tree(schema, tree, level)
    puts
    puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
    puts "level=#{level} tree=#{tree}"
    case schema['type']
    when 'object'
        puts "in when object for #{schema['properties'].size} properties :"
        i = 0
        schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
        schema['properties'].each do | property_name, property_schema |
            puts "object level=#{level}, property_name=#{property_name}"
            type, sub_tree = build_tree(property_schema, {children: []}, level + 1)
            puts "object level=#{level} after recursion, type=#{type} sub_tree=#{sub_tree}"
            child = { name: property_name + type }
            child[:children] = sub_tree unless sub_tree.empty?
            tree[:children] << child
        end
        puts "object level=#{level} about to return tree=#{tree}"
        tree
    when 'array'
        puts "in when array"
        case schema['items']
        when Array
            puts "in when Array for #{schema['items'].size} items"
            i     = 0
            items = []
            schema['items'].each do | a_hash |
                puts "item #{i+=1} has #{a_hash.keys.size} keys :"
                a_hash.keys.each{ | key | puts key }
                # if the item has "type"=>"object" and "properties"=>{ ... }, then
                # the whole item must be passed as argument to the next recursion
                puts "level=#{level} about to recurs for item #{i}"
                answer = build_tree(a_hash, {children: []}, level + 1)
                puts "level=#{level} after recurs, answer=#{answer}"
                items << { "item #{i}" => answer }
            end
            return ' (array)', items
        else
            puts "oops ! Array expected"
            "oops ! Array expected"
        end
    when 'string'
        puts "in when string, schema=#{schema}"
        return ' (string)', []
    else
        puts "in else"
        tree[:name] == schema
    end
end

build_tree(schema, tree, 1)
puts 'final result :'
puts tree
结果编辑:

{"name"=>"200", 
 :children=>[
   {
     :name=>"books (array)",
     :children=>[
       {"item 1"=>{
         :children=>[
           {:name=>"urn (string)"}, 
           {:name=>"title (string)"}
         ]
        }
       },
       {"item 2"=>{
         :children=>[
           {:name=>"urn2 (string)"}, 
           {:name=>"title2 (string)"}
         ]
        }
       }
     ]
   }
 ]
}

@BernardK提出的解决方案在长度上令人印象深刻,但我无法让它发挥作用。这是我更谦虚的解决方案。我将它包装在一个类中,以便能够正确地测试它

代码的一个问题是,在几个地方,您返回的是
树[:name]==schema
,其计算结果为
false
。我想您的意思是分配
tree[:name]=schema
,然后返回
tree

像@BernardK一样,我假设“array”类型的模式的值是一个事物数组。如果这不是它的工作原理,那么您能否提供一个例子,说明“数组”不仅仅是“对象”周围的附加层

希望在这个答案和另一个答案之间,你能从中找到适合你的答案

# swagger.rb

class Swagger

  def self.build_tree(schema, tree)
    if schema.class == ActiveSupport::HashWithIndifferentAccess
      case schema['type']
      when 'object'
        tree['children'] = schema['properties'].map do |property_name, property_schema|
          build_tree(property_schema, {'name' => property_name})
        end
        tree
      when 'array'
        schema['items'].map do |item|
          build_tree(item, {'name' => "#{tree['name']} (array)"})
        end
      when 'string'
        {'name' => "#{tree['name']} (string)"}
      end
    else
      raise ArgumentError, "Expected a HashWithIndifferentAccess but got #{schema.class}: #{schema}"
    end
  end
end
以下是规范文件:

# /spec/swagger_spec.rb

require_relative '../swagger'

describe Swagger do
  describe '.build_tree' do
    context 'when given a Hash whose type is string' do
      let(:tree) { {"name" => "urn"} }
      let(:schema) { {"type" => "string"}.with_indifferent_access }
      let(:expected) { {"name" => "urn (string)"} }

      it 'returns a Hash with "name" as the key and the tree value and its type as the value' do
        expect(Swagger.build_tree(schema, tree)).to eq(expected)
      end
    end

    context 'when given a simple schema' do
      let(:tree) { {"name" => "200"} }
      let(:schema) { {"type" => "object",
                      "properties" => {
                          "urn" => {"type" => "string"},
                          "title" => {"type" => "string"}
                      }}.with_indifferent_access }

      let(:expected) { {"name" => "200",
                        "children" => [{"name" => "urn (string)"},
                                       {"name" => "title (string)"}
                        ]} }

      it 'transforms the tree into swagger (openAPI) format' do
        expect(Swagger.build_tree(schema, tree)).to eq(expected)
      end
    end

    context 'when given a complicated schema' do
      let(:tree) { {"name" => "200"} }
      let(:schema) { {"type" => "object",
                      "properties" =>
                          {"books" =>
                               {"type" => "array",
                                "items" =>
                                    [{"type" => "object",
                                      "properties" =>
                                          {"urn" => {"type" => "string"}, "title" => {"type" => "string"}}
                                     }] # <-- added brackets
                               }
                          }
      }.with_indifferent_access }

      let(:expected) { {"name" => "200",
                        "children" =>
                            [[{"name" => "books (array)",
                              "children" => [{"name" => "urn (string)"}, {"name" => "title (string)"}]
                              }]]
      } }

      it 'transforms the tree into swagger (openAPI) format' do
        expect(Swagger.build_tree(schema, tree)).to eq(expected)
      end
    end

    context 'when given a schema that is not a HashWithIndifferentAccess' do
      let(:tree) { {"name" => "200"} }
      let(:schema) { ['random array'] }

      it 'raises an error' do
        expect { Swagger.build_tree(schema, tree) }.to raise_error ArgumentError
      end
    end
  end
end
#/spec/swagger_spec.rb
需要相对“../swagger”
描述昂首阔步的行为
描述“.build_tree”做什么
上下文“当给定类型为字符串的哈希时”do
let(:tree){{“name”=>“urn”}
let(:schema){{“type”=>“string”}.with_interference_access}
let(:应为){{“name”=>“urn(string)”}
它'返回一个散列,其中“name”作为键,树值及其类型作为'do'值
expect(招摇过市。构建树(模式,树))。到eq(预期)
结束
结束
上下文“当给定一个简单模式时”执行
let(:tree){{“name”=>“200”}
let(:schema){{“type”=>“object”,
“属性”=>{
“urn”=>{“type”=>“string”},
“标题”=>{“类型”=>“字符串”}
}}.具有_冷漠_访问}
let(:应为){{“name”=>“200”,
“children”=>[{“name”=>“urn(string)”},
{“名称”=>“标题(字符串)”}
]} }
它“将树转换为swagger(openAPI)格式”
expect(招摇过市。构建树(模式,树))。到eq(预期)
结束
结束
上下文“当给定一个复杂的模式时”执行
let(:tree){{“name”=>“200”}
let(:schema){{“type”=>“object”,
“属性”=>
{“书籍”=>
{“类型”=>“数组”,
“项目”=>
[{“类型”=>“对象”,
“属性”=>
{“urn”=>{“type”=>“string”},“title”=>{“type”=>“string”}
}] #  "200",
“儿童”=>
[{“名称”=>“书籍(数组)”,
“children”=>[{“name”=>“urn(string)”},{“name”=>“title(string)”}]
}]]
} }
它“将树转换为swagger(openAPI)格式”
expect(招摇过市。构建树(模式,树))。到eq(预期)
结束
结束
上下文“如果给定的架构不是HashWithInferenceTaccess”do
let(:tree){{“name”=>“200”}
let(:schema){['random array']}
它会“引发错误”吗
期望{Swagger.build_tree(schema,tree)}。引发_错误
结束
结束
结束
结束

因此,项数组不是项数组,而是子架构的属性数组。这是考虑到这一事实的新解决方案:

schema = 
    { "type"=>"object",
      "properties"=>{
        "books"=>{
          "type"=>"array",
          "items"=> {
              "type"=>"object",
              "properties" => {
                "urn"   => { "type"=>"string" },
                "title" => { "type"=>"string" }
                              }
          } # end items
        } # end books
      } # end properties
    } # end schema

tree = {"name"=>"200"}

def build_tree(schema, tree, level)
    puts
    puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
    puts "level=#{level} tree=#{tree}"
    case schema['type']
    when 'object'
        puts "in when object for #{schema['properties'].size} properties :"
        i = 0
        schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
        tree[:children] = []
        schema['properties'].each do | property_name, property_schema |
            puts "object level=#{level}, property_name=#{property_name}"
            type, sub_tree = build_tree(property_schema, {}, level + 1)
            puts "object level=#{level} after recursion, type=#{type} sub_tree=#{sub_tree}"
            child = { name: property_name + type }
            sub_tree.each { | k, v | child[k] = v }
            tree[:children] << child
        end
        puts "object level=#{level} about to return tree=#{tree}"
        tree
    when 'array'
        puts "in when array"
        case schema['items']
        when Hash
            puts "in when Hash"
            puts "the schema has #{schema['items'].keys.size} keys :"
            schema['items'].keys.each{ | key | puts key }
            # here you could raise an error if the two keys are NOT "type"=>"object" and "properties"=>{ ... }
            puts "Hash level=#{level} about to recurs"
            return ' (array)', build_tree(schema['items'], {}, level + 1)
        else
            puts "oops ! Hash expected"
            "oops ! Hash expected"
        end
    when 'string'
        puts "in when string, schema=#{schema}"
        return ' (string)', {}
    else
        puts "in else"
        tree[:name] == schema # ???? comparison ?
    end
end

build_tree(schema, tree, 1)
puts 'final result :'
puts tree
别把它当成是精彩的代码,我在magni的每一次地震中都会写Ruby代码
{"name"=>"200", 
 :children=>[
   {
     :name=>"books (array)",
     :children=>[
       {"item 1"=>{
         :children=>[
           {:name=>"urn (string)"}, 
           {:name=>"title (string)"}
         ]
        }
       },
       {"item 2"=>{
         :children=>[
           {:name=>"urn2 (string)"}, 
           {:name=>"title2 (string)"}
         ]
        }
       }
     ]
   }
 ]
}
# swagger.rb

class Swagger

  def self.build_tree(schema, tree)
    if schema.class == ActiveSupport::HashWithIndifferentAccess
      case schema['type']
      when 'object'
        tree['children'] = schema['properties'].map do |property_name, property_schema|
          build_tree(property_schema, {'name' => property_name})
        end
        tree
      when 'array'
        schema['items'].map do |item|
          build_tree(item, {'name' => "#{tree['name']} (array)"})
        end
      when 'string'
        {'name' => "#{tree['name']} (string)"}
      end
    else
      raise ArgumentError, "Expected a HashWithIndifferentAccess but got #{schema.class}: #{schema}"
    end
  end
end
# /spec/swagger_spec.rb

require_relative '../swagger'

describe Swagger do
  describe '.build_tree' do
    context 'when given a Hash whose type is string' do
      let(:tree) { {"name" => "urn"} }
      let(:schema) { {"type" => "string"}.with_indifferent_access }
      let(:expected) { {"name" => "urn (string)"} }

      it 'returns a Hash with "name" as the key and the tree value and its type as the value' do
        expect(Swagger.build_tree(schema, tree)).to eq(expected)
      end
    end

    context 'when given a simple schema' do
      let(:tree) { {"name" => "200"} }
      let(:schema) { {"type" => "object",
                      "properties" => {
                          "urn" => {"type" => "string"},
                          "title" => {"type" => "string"}
                      }}.with_indifferent_access }

      let(:expected) { {"name" => "200",
                        "children" => [{"name" => "urn (string)"},
                                       {"name" => "title (string)"}
                        ]} }

      it 'transforms the tree into swagger (openAPI) format' do
        expect(Swagger.build_tree(schema, tree)).to eq(expected)
      end
    end

    context 'when given a complicated schema' do
      let(:tree) { {"name" => "200"} }
      let(:schema) { {"type" => "object",
                      "properties" =>
                          {"books" =>
                               {"type" => "array",
                                "items" =>
                                    [{"type" => "object",
                                      "properties" =>
                                          {"urn" => {"type" => "string"}, "title" => {"type" => "string"}}
                                     }] # <-- added brackets
                               }
                          }
      }.with_indifferent_access }

      let(:expected) { {"name" => "200",
                        "children" =>
                            [[{"name" => "books (array)",
                              "children" => [{"name" => "urn (string)"}, {"name" => "title (string)"}]
                              }]]
      } }

      it 'transforms the tree into swagger (openAPI) format' do
        expect(Swagger.build_tree(schema, tree)).to eq(expected)
      end
    end

    context 'when given a schema that is not a HashWithIndifferentAccess' do
      let(:tree) { {"name" => "200"} }
      let(:schema) { ['random array'] }

      it 'raises an error' do
        expect { Swagger.build_tree(schema, tree) }.to raise_error ArgumentError
      end
    end
  end
end
schema = 
    { "type"=>"object",
      "properties"=>{
        "books"=>{
          "type"=>"array",
          "items"=> {
              "type"=>"object",
              "properties" => {
                "urn"   => { "type"=>"string" },
                "title" => { "type"=>"string" }
                              }
          } # end items
        } # end books
      } # end properties
    } # end schema

tree = {"name"=>"200"}

def build_tree(schema, tree, level)
    puts
    puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
    puts "level=#{level} tree=#{tree}"
    case schema['type']
    when 'object'
        puts "in when object for #{schema['properties'].size} properties :"
        i = 0
        schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
        tree[:children] = []
        schema['properties'].each do | property_name, property_schema |
            puts "object level=#{level}, property_name=#{property_name}"
            type, sub_tree = build_tree(property_schema, {}, level + 1)
            puts "object level=#{level} after recursion, type=#{type} sub_tree=#{sub_tree}"
            child = { name: property_name + type }
            sub_tree.each { | k, v | child[k] = v }
            tree[:children] << child
        end
        puts "object level=#{level} about to return tree=#{tree}"
        tree
    when 'array'
        puts "in when array"
        case schema['items']
        when Hash
            puts "in when Hash"
            puts "the schema has #{schema['items'].keys.size} keys :"
            schema['items'].keys.each{ | key | puts key }
            # here you could raise an error if the two keys are NOT "type"=>"object" and "properties"=>{ ... }
            puts "Hash level=#{level} about to recurs"
            return ' (array)', build_tree(schema['items'], {}, level + 1)
        else
            puts "oops ! Hash expected"
            "oops ! Hash expected"
        end
    when 'string'
        puts "in when string, schema=#{schema}"
        return ' (string)', {}
    else
        puts "in else"
        tree[:name] == schema # ???? comparison ?
    end
end

build_tree(schema, tree, 1)
puts 'final result :'
puts tree
{ "name"=>"200", 
  :children=> [
    {
      :name=>"books (array)", 
      :children=> [
        {:name=>"urn (string)"}, 
        {:name=>"title (string)"}
      ]
    }
  ]
}
some_key = some_data # sometimes string, sometimes symbol
schema[some_key]...
some_key = some_data # sometimes string, sometimes symbol
schema[some_key.to_sym]...
some_key = some_data # sometimes string, sometimes symbol
schema[some_key.to_s]...