与红宝石
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 }
顺便说一句,我正在做一个Ruby脚本谷歌Sketchup的
但是,如果我运行此代码,我只得到
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
这意味着无论什么时候第一次遇到特定的材料(很明显,这在循环的第一次迭代中已经是这种情况),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 = Hash.new(0)
替换summa = {}
(这将是写作它的惯用方式)。现在,代码至少工程。
作为下一个步骤,我会切换两个局部变量的赋值:第一分配entities
,然后分配summa
,那么你做的东西跟entities
,你要看看三行高达弄清楚什么entities
是。如果您切换这两个,entities
的使用和分配紧挨着。
因此,我们看到entities
被分配,然后立即使用,然后再也没有使用过。我不认为这多少增加了可读性,所以我们可以得到完全摆脱它:
for face in Sketchup.active_model.entities
接下来是for
循环。这些是高度在Ruby中非惯用; Rubyists强烈偏好内部迭代器。所以,让我们切换到一个:
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中,只有模块主体,类主体,方法体,块体和脚本机构都有自己的范围; for
和while
循环体以及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? C
相同C === 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语言,计划,Clojure中,哈斯克尔,二郎,F#,Scala中,&hellip一些经验;基本上所有任何现代语言,将立即认识到这种模式的catamorphism,reduce
,fold
,inject:into:
,inject
或任何您所选择的语言中一样调用它。
一个reduce
做什么,基本上是“减少”几件事情到了一两件事。最明显的例子是数字列表的总和:它减少了几个号码到一个号码:
[4, 8, 15, 16, 23, 42].reduce(0) {|accumulator, number| accumulator += number }
[注:在惯用的红宝石,这将被写入就像[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
在这种情况下,accumulator
是summa
哈希值。
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 }
}
而且,就是这样!
summa[face.material.display_name]
返回nil时,默认情况face.material.display_name不是现有的密钥。创建散列时,您可以指定一个不同的默认值来返回。喜欢的东西:
summa = Hash.new(0)
Thx很多家伙,这真的帮助了我很多工作;) – 2010-05-24 11:20:40
你的脸区域的总结只是注意 - 你还必须考虑到组/部件可能被缩放,所以你需要使用包含您检查面组/部件的整个层次的变革。请记住,组/组件也可能会发生倾斜 - 因此也必须考虑到这一点。
Omg,这是完全值得的阅读,并thx所有提示和更正我的代码。如果它没有渗透,我是Ruby的初学者,也是Sketchup的插件编程人员。 – 2010-05-25 19:06:54
优秀! +1是不够的! :P我希望我有耐心去做你刚刚做的事情。 – 2010-05-25 19:45:49
如果我要更改代码来计算双方的材料,那么最好的方法是怎么做的呢? back_material和材料是。 – 2010-05-26 06:04:33