在 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
Dynamically creating a multi-dimensional hash in Ruby
提问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

