問題是這樣的:
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 }
}
而且,就是這樣!
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