2010-05-24 16 views
2
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 

Я пытаюсь получить структуру в массиве, как, например:Обобщить объект области с Hash в Руби

summa { "Bricks" => 500, "Planks" => 4000 } 

Кстати, я делаю рубиновый скрипт для Google 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?

Так что любая помощь в том, как мне нужно идти, будет приятной.

ответ

7

Проблема заключается в следующем:

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 = {} (что BTW было бы идиоматическим способом написать) с summa = Hash.new(0). Теперь код не менее работает.

В качестве следующего шага, я переключил бы назначение двух локальных переменных: сначала назначить entities, а затем присвоить summa, то вы делаете что-то с entities, и вы должны смотреть три строки вверх, чтобы выяснить, что entities было , Если вы переключите два, использование и назначение entities находятся рядом друг с другом.

В результате мы видим, что назначается entities, затем сразу же используется, а затем никогда не используется снова. Я не думаю, что это улучшает читаемость много, так что мы можем избавиться от него вообще:

for face in Sketchup.active_model.entities 

Далее идет цикл for. Это высоко un-idiomatic в 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 модуль только тела, классовые тела, тела методов, блок тела и скриптовых тела имеют свои собственные сферы;. for и while тела цикла, а также if/unless/case выражения не)

Давайте на к телу петли.

Первая строка - это пункт охраны. Это хорошо, мне нравятся защитные оговорки :-)

Вторая строка, ну, если face.material истинно-иш, она делает что-то в противном случае, она ничего не делает, а это значит, что цикл закончен. Итак, это еще охранник clause! Тем не менее, он написан в полностью разных стилей, чем первое предложение охраны, прямо на одну строку над ним! Опять же, важна непротиворечивость:

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, Scheme, Clojure, Haskell, Erlang, F #, Scala, & hellip; в основном любой современный язык, сразу признают этот шаблон как катаморфизм, reduce, fold, inject:into:, inject или каким бы то ни было вашим выбором языка.

Что делает reduce, в основном это «сводит» несколько вещей в одну вещь. Наиболее очевидным примером является сумма списка чисел: уменьшает несколько номеров в только один номер:

[4, 8, 15, 16, 23, 42].reduce(0) {|accumulator, number| accumulator += number } 

[Примечание:. В идиоматических Ruby, это было бы написано так же, как [4, 8, 15, 16, 23, 42].reduce(:+)]

One способ обнаружить 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 } 
} 

И вот оно!

+0

Омг, это было совершенно необходимо прочитать, и thx для всех подсказок и исправлений к моему коду. Если он не просочился, я стал новичком в Ruby, а также программировал плагины для Sketchup. –

+1

Отлично! +1 недостаточно! : P Мне жаль, что у меня не было терпения делать то, что вы только что сделали. –

+0

Если бы я должен был изменить код для подсчета материала с обеих сторон, как наилучшим образом это сделать? back_material и материал, который есть. –

2

summa[face.material.display_name] возвращает nil по умолчанию, когда face.material.display_name не является существующим ключом. При создании хэша вы можете указать другое значение по умолчанию для возврата. Что-то вроде:

summa = Hash.new(0) 
+0

Thx много чувак, это действительно помогло мне много работы;) –

0

Просто обратите внимание на сводку областей лица - вы также должны учитывать размеры групп/компонентов, поэтому вам необходимо использовать преобразования всей иерархии групп/компонентов, содержащих лицо, которое вы проверяете. Помните, что группы/компоненты также могут быть перекошены, что также необходимо учитывать.

Смежные вопросы