红宝石中的递归哈希变换函数 [英] Recursive Hash transformation function in ruby

查看:117
本文介绍了红宝石中的递归哈希变换函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个响应模式的swagger(openAPI)定义:

  h = {type=> 对象,
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}

  def build_tree(schema,tree)
如果schema.class == ActiveSupport :: HashWithIndifferentAccess
case schema [:type ]
当'object'
tree [:children] = []
schema [:properties] .each do | property_name,property_schema |
tree [:children]<<
{name:property_name,children:build_tree(property_schema,tree)}
结束
'array'
schema [:items] .each do | property_name,property_schema |
tree [:children]<<
{name:property_name,children:build_tree(property_schema,tree)}
end
当nil
tree [:name] == schema
end
else
tree [:name] == schema
end
end



<不幸的是我觉得我在某个地方犯了一个错误,因为这会返回以下哈希:

  {:name => 200,
:children => [
{:name =>type,:children => false},
{:name =>properties, :children => false},
{:name =>books,
:children => {
type=>object,
properties=> {
urn=> {type=>string},
title=> {type=>string}
}
}
}
]
}

我必须错过递归中的一个步骤或以错误的方式传递树,但是我担心我没有足够的智力来弄清楚:)也许一个有着编写美丽的红宝石代码的好心的人会给我一只手! 解决方案因此,数组项不是一个项目数组,而是一个子模式的属性数组。这是考虑到这一事实的新解决方案:

  schema = 
{type=>对象,
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
putslevel =# {level} schema [:type] =#{schema ['type']。inspect},模式类是#{schema.class}
putslevel =#{level} tree =#{tree}
case schema ['type']
当'object'
puts'在#{schema ['properties']。size}属性的对象时:
i = 0
schema ['properties']。each_key {|名称| puts#{i + = 1}。#{name}}
tree [:children] = []
schema ['properties']。each do | property_name,property_schema |
putsobject level =#{level},property_name =#{property_name}
type,sub_tree = build_tree(property_schema,{},level + 1)
putsobject level =# {level}在递归之后,键入=#{type} sub_tree =#{sub_tree}
child = {name:property_name + type}
sub_tree.each {| k,v | child [k] = v}
tree [:children]<< child
end
当'array'
puts'in'时,将object level =#{level}放回到tree =#{tree}
tree
数组
case schema ['items']
Hash
putsin when Hash
puts架构有#{schema ['items']。keys.size }键:
schema ['items'] .keys.each {|键|如果两个键不是type=>object和properties=> {...}
puts哈希级别= #{level}即将重复出现
return'(array)',build_tree(schema ['items'],{},level + 1)
else
putsoops!Hash expected
oops!哈希预期
结束
当'string'
puts'in string,schema =#{schema}
return'(string) ',{}
else
将in else放入
tree [:name] == schema#????比较?
end
end

build_tree(schema,tree,1)
puts'final result:'
puts tree

$ b

编辑结果(用ruby 2.3.3p222测试):

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

不要把它当成辉煌的代码。我在每个12级的地震中编写Ruby代码。目的是解释代码中不起作用的内容,并提请注意在递归调用中使用新变量(现在是空Hash)。有很多情况下应该测试并引发错误。

正确的方法是像@moveson一样使用BDD:首先为所有情况编写RSpec测试,特别是边缘情况,然后编写代码。我知道它给人的感觉太慢了,但从长远来看,它支付并取代了痕迹的调试和打印。



strong>



这段代码很脆弱:例如,如果一个类型键与一个属性键没有关联,它将在 schema ['properties '] .each 对于nil:NilClass ,未定义方法'each'。如果一个类型对象没有任何属性,那么像
context这样的规则可以做
let(:schema){{type=> 对象,xyz=> ...



有助于添加代码来检查前提条件。我也懒得使用RSpec作为小脚本,但对于严重的开发,我会付出努力,因为我已经认识到了这些好处。花费在调试中的时间永远丢失,投入规格的时间可以在出现变化时提供安全性,并且可以提供关于代码执行或不执行的清晰可读的报告。我推荐全新的 Rspec 3书



有关访问哈希的更多信息:如果您有字符串和符号的组合,这是问题的根源。

  some_key = some_data#有时是字符串,有时是符号
schema [some_key] ...

$ b $如果内部键与外部数据的类型不同,b

将不会找到该元素。

  some_key = some_data#在创建哈希时选择一个类型,例如符号,并且系统地将访问变量转换为符号:有时是字符串,有时符号
schema [some_key.to_sym] ...

或全部字符串:

$ p $ some_key = some_data#有时字符串,有时符号
schema [some_key.to_s] ...


I have the following swagger (openAPI) definition of a response schema:

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

And would like to transform this into the following format, so as to be able to display this response as a tree:

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

In the swagger schema format, a node can either be an object (with properties), or an array of items, which are themselves objects. Here is the function I have written: the schema parameter is the hash in the swagger format shown above, and the tree variable contains {name: "200"}

      def build_tree(schema, tree)
        if schema.class == ActiveSupport::HashWithIndifferentAccess
          case schema[:type]
          when 'object'
            tree[:children] = []
            schema[:properties].each do |property_name, property_schema|
               tree[:children] <<
                 { name: property_name, children: build_tree(property_schema, tree) }
            end
          when 'array'
            schema[:items].each do |property_name, property_schema|
              tree[:children] <<
                { name: property_name, children: build_tree(property_schema, tree) }
            end
          when nil
            tree[:name] == schema
          end
          else
            tree[:name] == schema
          end
        end

Unfortunately I think I'm making an error somewhere, as this returns the following Hash:

{ :name=>"200",
  :children=>[
    { :name=>"type", :children=>false },
    { :name=>"properties", :children=>false },
    { :name=>"books",
      :children=>{
        "type"=>"object",
        "properties"=>{
          "urn"=>{"type"=>"string"},
          "title"=>{"type"=>"string"}
        }
      }
    }
  ]
}

I must be missing a step in the recursion or passing the tree in the wrong way, but I'm afraid I don't have enough brainpower to figure it out :) maybe a kind soul with a gift for writing beautiful ruby code will give me a hand!

解决方案

So array of items is not an array of items but an array of properties of the sub-schema. This is the new solution to take this fact into account :

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

Result edited (tested with ruby 2.3.3p222) :

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

Don't take it as brilliant code. I write Ruby code every earthquake of magnitude 12. The purpose was to explain what didn't work in your code and draw attention on using new variables (now an empty Hash) in the recursive call. There are plenty of cases that should be tested and raise an error.

The right way is BDD as did @moveson : first write RSpec tests for all cases, especially edge cases, then write the code. I know it gives the feeling to be too slow, but in the long term it pays and replaces the debugging and printing of traces.

More on tests

This code is brittle : for example if a type key is not associated with a properties key, it will fail at schema['properties'].each : undefined method 'each' for nil:NilClass. A spec like context 'when a type object has no properties' do let(:schema) { {"type" => "object", "xyz" => ...

would help adding code to check pre-conditions. I'm also lazy using RSpec for small scripts, but for serious development, I make the effort because I have recognized the benefits. The time spent in debugging is lost forever, the time invested in specs gives security in case of changes and a nice legible indented report about what the code does or does not. I recommend the brand new Rspec 3 book.

One more word on accessing hashes : if you have a mix of strings and symbols, it's a source of problems.

some_key = some_data # sometimes string, sometimes symbol
schema[some_key]...

will not find the element if the internal keys are not of the same type as the external data. Choose a type, for example symbol, when you create the hash, and systematically convert access variables to symbol :

some_key = some_data # sometimes string, sometimes symbol
schema[some_key.to_sym]...

or all to strings :

some_key = some_data # sometimes string, sometimes symbol
schema[some_key.to_s]...

这篇关于红宝石中的递归哈希变换函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆