gpt4 book ai didi

ruby - 用Ruby中的哈希值总结对象区域

转载 作者:行者123 更新时间:2023-12-02 06:44:12 25 4
gpt4 key购买 nike

require 'sketchup'

entities = Sketchup.active_model.entities
summa = Hash.new

for face in entities
next unless face.kind_of? Sketchup::Face
if (face.material)
summa[face.material.display_name] += face.area
end
end

我正在尝试这样获取数组中的结构:
summa { "Bricks" => 500, "Planks" => 4000 }

顺便说一句,我正在为Google Sketchup编写一个ruby脚本

但是如果我运行这段代码,我只会得到
Error: #<NoMethodError: undefined method `+' for nil:NilClass>
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:17
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:14:in `each'
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:14
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:8:in `call'

正如我习惯使用PHP并只是做 $array['myownassoc'] += bignumber;但是我想这不是使用Ruby时的正确方法吗?

因此,对我需要去的任何帮助都会很好。

最佳答案

问题是这样的:

summa[face.material.display_name] += face.area

(大致)相当于
summa[face.material.display_name] = summa[face.material.display_name] + face.area

但是,您首先使用 summa作为空散列:
summa = Hash.new

这意味着,每当您第一次遇到特定 Material 时(显然,在循环的第一次迭代中就已经是这种情况了), summa[face.material.display_name]根本就不存在。因此,您正在尝试为不存在的内容添加数字,这显然是行不通的。

快速解决方案是仅使用默认值初始化哈希,以便它为不存在的键返回有用的信息,而不是 nil:
summa = Hash.new(0)

但是,可以对代码进行许多其他改进。这是我的处理方式:
require 'sketchup'

Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).
reduce(Hash.new(0)) {|h, face|
h.tap {|h| h[face.material.display_name] += face.area }
}

我发现它更容易阅读,而不是“循环遍历,但是如果发生这种情况,则跳过一次迭代,如果发生这种情况,也不要这样做”。

这实际上是一种常见的模式,几乎每个Rubyist都已经写了十二遍,所以实际上我周围有一个代码片段,我只需要稍微适应一下即可。但是,我将向您展示如果我还没有解决方案,该如何逐步重构原始代码。

首先,让我们从编码风格开始。我知道这很无聊,但这很重要。实际的编码风格是什么,并不重要,重要的是代码是一致的,这意味着一段代码应该与任何其他代码看起来相同。在这种特定情况下,您要求Ruby社区提供无偿支持,因此至少以该社区成员习惯的格式格式化代码是有礼貌的。这意味着标准的Ruby编码样式:2个空格用于缩进,snake_case用于方法和变量名,CamelCase用于引用模块或类的常量,ALL_CAPS用于常量,等等。除非括号消除了优先级,否则不要使用括号。

例如,在您的代码中,有时使用3个空格,有时使用4个空格,有时使用5个空格,有时使用6个空格进行缩进,而所有这些仅用9行非空代码!您的编码风格不仅与社区其他人不一致,甚至与自己的下一行不一致!

让我们先解决这个问题:
require 'sketchup'
entities = Sketchup.active_model.entities
summa = {}

for face in entities
next unless face.kind_of? Sketchup::Face
if face.material
summa[face.material.display_name] += face.area
end
end

嗯,好多了。

正如我已经提到的,我们需要做的第一件事就是解决一个明显的问题:用 summa = {}替换 summa = Hash.new(0)(顺便说一句BTW是写它的方式)。现在,该代码至少可以正常工作。

下一步,我将切换两个局部变量的分配:首先分配 entities,然后分配 summa,然后对 entities进行操作,并且必须查看三行以找出 entities是什么。如果将两者切换, entities的用法和分配就紧挨着。

结果,我们看到 entities被分配,然后立即使用,然后再也不使用。我认为这不会大大提高可读性,因此我们可以完全摆脱它:
for face in Sketchup.active_model.entities

接下来是 for循环。这些在Ruby中非常不习惯。 Ruby主义者强烈喜欢内部迭代器。因此,让我们切换到一个:
Sketchup.active_model.entities.each {|face|
next unless face.kind_of? Sketchup::Face
if face.material
summa[face.material.display_name] += face.area
end
}

这样做的一个优点是,现在 face在循环主体中是本地的,而以前,它泄漏到了周围的作用域中。 (在Ruby中,只有模块主体,类主体,方法主体,块主体和脚本主体才具有它们自己的作用域; forwhile循环主体以及 if / unless / case表达式则没有。)

让我们进入循环的主体。

第一行是保护子句。很好,我喜欢警卫条款:-)

第二行是,如果 face.material是true-ish,则执行其他操作,否则不执行任何操作,这意味着循环结束。因此,这是另一个保护条款!但是,它的写法与第一个保护子句完全不同,直接在其上面一行!同样,一致性很重要:
Sketchup.active_model.entities.each {|face|
next unless face.kind_of? Sketchup::Face
next unless face.material
summa[face.material.display_name] += face.area
}

现在,我们有两个紧挨着的保护子句。让我们简化逻辑:
Sketchup.active_model.entities.each {|face|
next unless face.kind_of? Sketchup::Face && face.material
summa[face.material.display_name] += face.area
}

但是现在只有一个单一的保护子句,只能保护一个单一的表达式。因此,我们可以使整个表达式本身成为条件:
Sketchup.active_model.entities.each {|face|
summa[face.material.display_name] += face.area if
face.kind_of? Sketchup::Face && face.material
}

但是,这仍然很丑陋:我们正在遍历某些集合,然后在循环内跳过所有我们不想遍历的项目。因此,如果我们不想循环遍历它们,我们是否首先就循环遍历它们?我们不是先选择“有趣”的项目,然后再将它们循环浏览?
Sketchup.active_model.entities.select {|e|
e.kind_of? Sketchup::Face && e.material
}.each {|face|
summa[face.material.display_name] += face.area
}

我们可以对此进行一些简化。如果我们意识到 o.kind_of? CC === o相同,那么我们可以使用 grep过滤器,该过滤器使用 ===进行模式匹配,而不是 select:
Sketchup.active_model.entities.grep(Sketchup::Face).select {|e| e.material
}.each { … }

我们的 select过滤器可以通过使用 Symbol#to_proc进一步简化:
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).each { … }

现在让我们回到循环。任何对高级语言有一定经验的人,例如Ruby,JavaScript,Python,C++ STL,C#,Visual Basic.NET,Smalltalk,Lisp,Scheme,Clojure,Haskell,Erlang,F#,Scala……基本上是任何现代语言完全会立即将此模式识别为catatism, reducefoldinject:into:inject或您所选择的任何语言。
reduce所做的基本上是将几件事“简化”为一件事情。最明显的例子是一个数字列表的总和:它将几个数字减少为一个数字:
[4, 8, 15, 16, 23, 42].reduce(0) {|accumulator, number| accumulator += number }

[注意:在惯用的Ruby中,这将被编写为 [4, 8, 15, 16, 23, 42].reduce(:+)。]

发现潜伏在循环后面的 reduce的一种方法是寻找以下模式:
accumulator = something # create an accumulator before the loop

collection.each {|element|
# do something with the accumulator
}

# now, accumulator contains the result of what we were looking for

在这种情况下, accumulatorsumma哈希。
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).
reduce(Hash.new(0)) {|h, face|
h[face.material.display_name] += face.area
h
}

最后但并非最不重要的一点是,我不喜欢在块末尾显式返回 h。我们显然可以将其写在同一行上:
h[face.material.display_name] += face.area; h

但是我更喜欢使用 Object#tap(又名K组合器):
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).
reduce(Hash.new(0)) {|h, face|
h.tap {|h| h[face.material.display_name] += face.area }
}

而且,就是这样!

关于ruby - 用Ruby中的哈希值总结对象区域,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2896129/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com