在 Ruby 中动态创建多维散列

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10253105/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 05:02:58  来源:igfitidea点击:

Dynamically creating a multi-dimensional hash in Ruby

rubyhash

提问by Chris Allen Lane

I'm a PHP developer who's trying to gain some proficiency in Ruby. One of the projects I'm cutting my teeth on now is a source-code auditing tool that scans webapp files for potentially dangerous functions in several web programming languages. When matches are found, the script saves the relevant information in a poi(point-of-interest) class for display later on.

我是一名 PHP 开发人员,正在尝试精通 Ruby。我现在正在研究的项目之一是一个源代码审计工具,它扫描 webapp 文件以查找几种 web 编程语言中的潜在危险功能。找到匹配项后,脚本会将相关信息保存在poi(兴趣点)类中以供稍后显示。

An example instance of that class would look something like this (modeled in YAML):

该类的示例实例如下所示(以 YAML 建模):

poi:
    file_type: "php"
    file: "the-scanned-file.php"
    line_number: 100
    match: "eval()"
    snippet: "echo eval()"

On display, I want to organize these points of interest like so:

在展示时,我想像这样组织这些兴趣点:

- file_type
-- file
--- match (the searched payload)

Thus, before presentation, I'm trying to structure a flat array of poiobjects into a hash mirroring the structure above. This will allow me to simply iterate over the items in the hash to produce the desired on-screen organization. (Or at least, that's the plan.)

因此,在演示之前,我试图将poi对象的平面数组构建为反映上述结构的哈希。这将允许我简单地迭代散列中的项目以生成所需的屏幕组织。(或者至少,这是计划。)

And now, for my question: how do I do that in Ruby?

现在,对于我的问题:我如何在 Ruby 中做到这一点?

In PHP, I could do something like this really easily:

在 PHP 中,我可以很容易地做这样的事情:

<?php

$sorted_pois = array();
foreach($points_of_interest as $point){
    $sorted_pois[$point->file_type][$point->file][$point->match][] = $point;
}

?>

I've tried translating that thought from PHP to Ruby like this, but to no avail:

我试过像这样将这个想法从 PHP 翻译成 Ruby,但无济于事:

sorted_pois = {}
@points_of_interest.each_with_index do |point, index|
    sorted_pois[point.file_type.to_sym][point.file.to_sym][point.match.to_sym].push point
end

I've spent a few hours on this, and I'm kind of banging my head against the wall at this point, so presumably I'm way off-base. What's the proper way to handle this in Ruby?

我在这上面花了几个小时,在这一点上我的头有点撞墙,所以大概我离基地很远。在 Ruby 中处理这个问题的正确方法是什么?

Update:

更新:

For reference, this is the precise method I have defined:

作为参考,这是我定义的精确方法:

# sort the points of interest into a structured hash
def sort
  sorted_pois = {}
  @points_of_interest.each_with_index do |point, index|
    sorted_pois[point.file_type.to_sym][point.file.to_sym][point.match.to_sym].push point
  end
end

This is the error I receive when I run the code:

这是我运行代码时收到的错误:

./lib/models/vulnscanner.rb:63:in `sort': undefined method `[]' for nil:NilClass (NoMethodError)
    from /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `each_with_index'
    from ./lib/models/vulnscanner.rb:62:in `each'
    from ./lib/models/vulnscanner.rb:62:in `each_with_index'
    from ./lib/models/vulnscanner.rb:62:in `sort'
    from ./webapp-vulnscan:69

Line 62 (as you can likely infer) is this line in particular:

第 62 行(您可能会推断出)特别是这一行:

@points_of_interest.each_with_index do |point, index|

As an additional reference, here's what (a snippet of) @points_of_interestlooks like when converted to YAML:

作为附加参考,以下是@points_of_interest转换为 YAML 时的(片段)外观:

- !ruby/object:PoI 
  file: models/couponkimoffer.php
  file_type: php
  group: :dangerous_functions
  line_number: "472"
  match: `
  snippet: ORDER BY `created_at` DESC
- !ruby/object:PoI 
  file: models/couponkimoffer.php
  file_type: php
  group: :dangerous_functions
  line_number: "818"
  match: `
  snippet: WHERE `company_slug` = '$company_slug'
- !ruby/object:PoI 
  file: models/couponkimoffer.php
  file_type: php
  group: :dangerous_functions
  line_number: "819"
  match: `
  snippet: ORDER BY `created_at` DESC

回答by Phrogz

@John's Enumerable#group_bysuggestion is one good way to solve your needs. Another would be to create an auto-vivifying Hash (like you appear to have in PHP) like so:

@John 的Enumerable#group_by建议是解决您需求的一种好方法。另一种方法是创建一个自动激活的哈希(就像你在 PHP 中的样子),如下所示:

hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
hash[:a][:b][:c] = 42
p hash
#=> {:a=>{:b=>{:c=>42}}}

Note that this sort of auto-vivification can be 'dangerous' if you access keys that don't exist, as it creates them for you:

请注意,如果您访问不存在的密钥,这种自动激活可能是“危险的”,因为它会为您创建它们:

p hash["does this exist?"]
#=> {}

p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}

You can still use the vivifying default_procwithout hitting this danger if you use key?to test for the key first:

default_proc如果您先用于key?测试密钥,您仍然可以使用 viviifying而不会遇到这种危险:

val = hash["OH NOES"] if hash.key?("OH NOES")
#=> nil

p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}


FWIW, the error you are getting says, "Hey, you put []after something that evaluated to nil, and nildoesn't have a []method."Specifically, your code...

FWIW,你得到的错误说,“嘿,你把[]评估为 的东西放在后面nil,并且nil没有[]方法。” 具体来说,您的代码...

sorted_pois[point.file_type.to_sym]

evaluated to nil(because the hash did not yet have a value for this key) and then you attempted to ask for

评估为nil(因为散列还没有这个键的值),然后你试图要求

nil[point.file.to_sym]

回答by John Ledbetter

You might be interested in group_by.

您可能对group_by感兴趣。

Sample usage:

示例用法:

birds = ["Golden Eagle", "Gyrfalcon", "American Robin",
         "Mountain BlueBird", "Mountain-Hawk Eagle"]
grouped_by_first_letter = birds.group_by { |s| s[0] }

# { "G"=>["Golden Eagle", "Gyrfalcon"], "A"=>["American Robin"],
#   "M"=>["Mountain BlueBird", "Mountain-Hawk Eagle"] }

回答by Lukas Stejskal

The obvious problem with the example above is that nested hashes and arrays you try to use don't exist. Try this:

上面示例的明显问题是您尝试使用的嵌套散列和数组不存在。尝试这个:

sorted_pois = {}
pois.each do |point|
  # sanitize data - convert to hash of symbolized keys and values
  poi = Hash[ %w{file_type file match}.map do |key| 
    [key.to_sym, point.send(key).to_sym]
  end ]

  # create nested hash/array if it doesn't already exist
  sorted_pois[ poi[:file_type] ] ||= {}
  sorted_pois[ poi[:file_type] ][ poi[:file] ] ||= {}
  sorted_pois[ poi[:file_type] ][ poi[:file] ][ poi[:match] ] ||= []

  sorted_pois[ poi[:file_type] ][ poi[:file] ][ poi[:match] ] << point
end