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
I’m trying to get the structure in the array as such:
summa { "Bricks" => 500, "Planks" => 4000 }
By the way, I’m making a ruby script for Google Sketchup
But if I run this code I only get
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'
As I’m used to using PHP and just doing $array['myownassoc'] += bignumber;
But I guess this isn’t the right approach when using Ruby?
So any help in how I need to go would be nice.
The problem is this:
This is (roughly) equivalent to
However, you start out with
summaas an empty hash:Which means that whenever you encounter a specific material for the first time (and obviously, this is going to be already the case in the very first iteration of the loop),
summa[face.material.display_name]simply doesn’t exist. So, you are trying to add a number to something that doesn’t exist, which obviously cannot work.The quick fix would be to just initialize the hash with a default value, so that it returns something useful instead of
nilfor a non-existing key:There are, however, a lot of other improvements that could be made to the code. Here’s how I would do it:
I find that much easier to read, instead of “loop over this, but skip one iteration if that thing happens, and also don’t do this if that happens”.
This is actually a common pattern, that pretty much every Rubyist has already written a dozen times, so I actually had a code snippet lying around that I only needed to slightly adapt. However, I am going to show you how I could have refactored your original code step-by-step if I hadn’t already had the solution.
First, let’s start with coding style. I know it’s boring, but it is important. What the actual coding style is, is not important, the important thing is that the code is consistent, which means that one piece of code should look the same as any other piece of code. In this particular instance, you are asking the Ruby community to provide you with unpaid support, so it is polite to at least format the code in a style that members of that community are used to. This means standard Ruby coding style: 2 spaces for indentation, snake_case for method and variable names, CamelCase for constants which refer to modules or classes, ALL_CAPS for constants, and so on. Don’t use parentheses unless they clear up the precedence.
In your code, for example, you use sometimes 3 spaces, sometimes 4 spaces, sometimes 5 spaces and sometimes 6 spaces for indentation, and all of that in just 9 non-empty lines of code! Your coding style is not only inconsistent with the rest of the community, it isn’t even consistent with its own next line!
Let’s fix that first:
Ah, much better.
As I already mentioned, the first thing we need to do, is fix the obvious problem: replace
summa = {}(which BTW would be the idiomatic way to write it) withsumma = Hash.new(0). Now, the code at least works.As a next step, I would switch the assignment of the two local variables: first you assign
entities, then you assignsumma, then you do something withentitiesand you have to look three lines up to figure out whatentitieswas. If you switch the two, the usage and the assignment ofentitiesare right next to each other.As a result, we see that
entitiesis assigned, then immediately used and then never used again. I don’t think this improves readability much, so we can get rid of it altogether:Next comes the
forloop. Those are highly un-idiomatic in Ruby; Rubyists strongly prefer internal iterators. So, let’s switch to one:One advantage this has, is that now
faceis local to the body of the loop, whereas before, it was leaking out into the surrounding scope. (In Ruby, only module bodies, class bodies, method bodies, block bodies and script bodies have their own scope;forandwhileloop bodies as well asif/unless/caseexpressions don’t.)Let’s get on to the body of the loop.
The first line is a guard clause. That’s good, I like guard clauses 🙂
The second line is, well, if
face.materialis true-ish, it does something otherwise it does nothing, which means the loop is over. So, it’s another guard clause! However, it is written in a totally different style than the first guard clause, directly one line above it! Again, consistency is important:Now we have two guard clauses right next to each other. Let’s simplify the logic:
But now there is only one single guard clause guarding only one single expression. So, we can just make the whole expression itself conditional:
However, that’s still kind of ugly: we are looping over some collection, and then inside the loop we skip over all the items we don’t want to loop over. So, if we don’t want to loop over them, we do we loop over them in the first place? We don’t we just select the “interesting” items first and then loop over just them?
We can do some simplification on this. If we realize that
o.kind_of? Cis the same asC === o, then we can use thegrepfilter which uses===to pattern match, instead ofselect:Our
selectfilter can further be simplified by usingSymbol#to_proc:Now let’s get back to the loop. Anybody who has some experience in a higher-order language such as Ruby, JavaScript, Python, C++ STL, C#, Visual Basic.NET, Smalltalk, Lisp, Scheme, Clojure, Haskell, Erlang, F#, Scala, … basically any modern language at all, will immediately recognize this pattern as a catamorphism,
reduce,fold,inject:into:,injector whatever your language of choice happens to call it.What a
reducedoes, is basically it “reduces” several things into just one thing. The most obvious example is the sum of a list of numbers: it reduces several numbers into just one number:[Note: in idiomatic Ruby, this would be written just as
[4, 8, 15, 16, 23, 42].reduce(:+).]One way to spot a
reducelurking behind a loop is to look for the following pattern:In this case, the
accumulatoris thesummahash.Last but not least, I don’t like this explicit returning of
hat the end of block. We could obviously write it on the same line:But I prefer the use of
Object#tap(aka the K-combinator) instead:And, that’s it!