Проблема заключается в следующем:
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 }
}
И вот оно!
Омг, это было совершенно необходимо прочитать, и thx для всех подсказок и исправлений к моему коду. Если он не просочился, я стал новичком в Ruby, а также программировал плагины для Sketchup. –
Отлично! +1 недостаточно! : P Мне жаль, что у меня не было терпения делать то, что вы только что сделали. –
Если бы я должен был изменить код для подсчета материала с обеих сторон, как наилучшим образом это сделать? back_material и материал, который есть. –