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