图元统计的新方法:自己写迭代器
在之前的几篇有关图元统计的文章中[SU-2020-08, SU-2020-11],为了能够统计图元的自定义属性,使用了字符串转方法名的 method_rank( obj, mtd) 方法。现在看来这样的做法还是太过于繁琐,也实在不够“优雅”、不够ruby。
因此现在补充一个更符合ruby风格、更“优雅”的做法。
现在重新回到最开始的需求:现有条件是一个筛选后的图元容器,可以 Sketchup:: Selection 类,也可以就是 Sketchup:: Entities 类,或者是通过这些容器类重新筛选出来的 Array 类。需要统计容器中的每一个元素的某一个属性,而这些属性可以通过其实例方法调用返回。
之前花了很大的功夫在想怎么赋予参数层次性,也就是例如 "volume. to_mm. to_mm. to_mm" 这样的属性应当如何用参数表示。但是其实完全可以就把这样的代码作为参数传递到方法中。
例如,迭代器 .each 就是一个将代码作为参数的方法。
sels = Sketchup.active_model.selection
sels.each{|e|puts e.typename}
puts sels.map(&:typename)
其中,代码 puts e.typename 就是参数,而花括号中 |e| 的声明就将代码中的 e 与选区的枚举过程联系在了一起。这样就不需要使用诸如 sels. each2( "puts", "e.typename") 这样的表达了。
所以关键就是要写一个类似于迭代器的方法,这也是本篇的核心内容。
首先尝试以下代码:
arr=(0..9).to_a
arr.each{|i|puts i}
#>> 输出0-9
p1=proc{|i|puts i}
arr.each(&p1)
#>> 输出0-9
p1.class
#>> Proc
可以发现,迭代器 .each 可以用中括号加代码的形式调用,也可以用“&”加一个Proc实例的形式调用。而这里的Proc实例就能够直接表示一段代码。
那么,现在就需要知道在方法定义中,这个特殊的参数是如何调用的。可以通过以下代码了解这类参数的引用方式:
class Array
def each2(&block)
index=0
while index < self.length
block.call(self[index])
index+=1
end
end
end
作为示范,以上代码定义了一个可以替代 .each 方法的 .each2 方法。可以从中发现,方法定义中每一个 block. call 都会调用依次 &block 中的代码,而如果 &block 中有形如 |i| 的自变量,就会将 .call 方法后的参数赋给 i。如果代码中有自变量,但是 .call 方法没有参数,就会使用 nil 补全,多余的参数则舍弃。因此 Proc 实例也可以要求更多的自变量,就像 .sort 方法一样。
那么统计图元属性就有了更好的选择,甚至可以直接将这种方法写进 Array 类当中。
class Array
def summarize(&block)
h=Hash.new
self.each{|i|
begin
res=block.call(i)
rescue
res="N\/A"
end
if h.has_key?(res) then
h[res]+=1
else
h[res]=1
end
}
return(h)
end
end
如下图所示,就可以直接对选区进行图元统计,图中的 S.ss 即 Sketchup. active_model. selection。
当然,此时的 summarize 方法只不过是返回了一个统计结果的哈希,如果要让它更具可读性,可以在 Hash 类中追加以下方法:
class Hash
def print(reg=//)
shown=0
total=0
self.each{|k,v|
if k.to_s=~reg or reg==// then
puts "#{k}".ljust(18)+"|#{v}"
shown+=1
end
total+=1
}
puts "------------------+----"
puts "Shown:".ljust(18)+"|#{shown}" if shown!=total
puts "Total:".ljust(18)+"|#{total}"
end
end
在追加了这两个方法之后就可以通过类似于以下的代码来统计图元信息了:
sels.to_a.summarize{|i|i.definition.name}.print(/House/)
以下为执行效果:
本文编号:SU-2021-02