Что такое грубый и точный поиск по сетке? - PullRequest
3 голосов
/ 18 января 2020

Я читал этот ответ

Эффективная (и хорошо объясненная) реализация Quadtree для 2D обнаружения столкновений

и встретила этот абзац

Хорошо, так что на самом деле квадриты не моя любимая структура данных для этой цели. Я предпочитаю предпочитать иерархию сеток, например, грубую сетку для мира, более мелкую сетку для региона и еще более мелкую сетку для субрегиона (3 фиксированных уровня плотных сеток и без деревьев), с рядными основанные на оптимизации, так что строка, в которой нет сущностей, будет освобождена и превращена в нулевой указатель, а также полностью пустые области или подобласти превращены в нули. Хотя эта простая реализация квадродерева, работающего в одном потоке, может обрабатывать 100 тыс. Агентов на моем i7 со скоростью более 60 FPS, я реализовал сетки, которые могут обрабатывать пару миллионов агентов, отскакивающих друг от друга каждый кадр на старом оборудовании (i3). Также мне всегда нравилось, как сетки позволяют легко предсказать, сколько памяти им потребуется, поскольку они не делят ячейки. Но я попытаюсь осветить, как реализовать достаточно эффективное квадродерево.

Этот тип сетки кажется интуитивным, он звучит как сетка "N-порядка", где вместо 4 дочерних узлов у вас есть N дочерних узлов на одного родителя. N ^ 3 может go намного дальше, чем 4 ^ 3, что позволяет повысить точность с потенциально (я полагаю) меньшим количеством ветвлений (поскольку существует намного меньше узлов для ветвления).

Я немного озадачен, потому что Я бы интуитивно использовал одну или, может быть, 3 std :: map с правильным < operator(), чтобы уменьшить объем памяти, но я не уверен, что это будет так быстро, так как запрос AABB будет означать суммирование нескольких обращений, которые O (log n).

Что именно это за оптимизация на основе строк, о которой он говорит? Является ли этот тип поиска по сетке обычным явлением?

У меня есть некоторое представление о кривой z-порядка, и я не совсем удовлетворен квадродеревом.

1 Ответ

2 голосов
/ 10 марта 2020

Это моя собственная цитата. Но это основано на общей схеме, с которой я столкнулся в своем личном опыте. Кроме того, такие термины, как «родитель» и «ребенок» - это те слова, которые я бы в значительной степени отбросил, говоря о сетках. Вы только что получили большой 2-мерный или N-мерный материал для хранения таблиц / матриц. На самом деле не существует никакой иерархии - эти структуры данных более сопоставимы с массивами, чем с деревьями.

"Грубо" и "Хорошо"

На "грубой" и «хорошо», я имел в виду, что «грубые» поисковые запросы, как правило, дешевле, но дают больше ложных срабатываний. Более грубая сетка будет с более низким разрешением сетки (меньше, больше ячеек). Грубый поиск может включать обход / поиск меньшего и большего количества ячеек сетки. Например, скажем, мы хотим видеть, пересекается ли элемент с точкой / точкой в ​​гигантской ячейке c (представьте себе только сетку 1x1, хранящую все в симуляции). Если точка пересекает ячейку, мы можем получить множество элементов, возвращаемых в эту ячейку, но, возможно, только один или ни один из них фактически не пересекает точку.

Таким образом, «грубый» запрос является широким и простым, но не очень точно сужает список кандидатов (или «подозреваемых»). Он может возвращать слишком много результатов и все же оставить большую обработку, необходимую для сужения того, что фактически пересекает параметр поиска *.

Это похоже на те детективные шоу, когда они ищут в базе данных возможный убийца, добавление «белого мужчины», возможно, не потребует большой обработки, чтобы перечислить результаты, но может дать слишком много результатов, чтобы должным образом сузить подозреваемых. «Хорошо» будет противоположным и может потребовать дополнительной обработки базы данных, но сузить результат до одного подозреваемого. Это грубая и ошибочная аналогия, но я надеюсь, что она поможет.

Часто ключ к широкой оптимизации пространственных индексов, прежде чем мы перейдем к таким вещам, как оптимизация памяти, независимо от того, говорим ли мы о пространственных хешах или квадродеревах, - это найти хороший баланс между "грубым" и "хорошим". Слишком «хорошо», и мы могли бы потратить слишком много времени на обход структуры данных (поиск множества маленьких ячеек в единой сетке или слишком много времени на обход дерева для адаптивных сеток, таких как квадри). Слишком «грубый» и пространственный индекс могут дать слишком много результатов, чтобы значительно сократить время, необходимое для дальнейшей обработки. Для пространственных хэшей (структура данных мне лично не очень нравится, но они очень популярны в gamedev), часто приходится много думать, экспериментировать и измерять, чтобы определить оптимальный размер ячейки для использования.

При использовании равномерных сеток NxM, насколько они "грубые" или "точные" (большие или маленькие ячейки и высокое или низкое разрешение сетки), не только влияет на время поиска запроса, но также может влиять на время вставки и удаления, если элементы больше точка. Если сетка слишком тонкая, один большой или средний элемент может быть вставлен во множество крошечных ячеек и удален из множества крошечных ячеек, используя много дополнительной памяти и обработки. Если он слишком грубый, элемент может быть вставлен и удален только в одну большую ячейку или из нее, но за счет способности структуры данных сузить число кандидатов, возвращаемых из поискового запроса, до минимума. Не обращая внимания, слишком «мелкий / гранулярный» процесс может стать очень узким на практике, и разработчик может найти свою сеточную структуру, используя гигабайты ОЗУ для скромного размера ввода. С вариантами дерева, такими как quadtree, подобное может произойти, если максимально допустимая глубина дерева слишком велика, что приводит к взрывному использованию памяти и обработке, когда листовые узлы quadtree хранят малейшие размеры ячеек (мы можем даже начать работать с плавающей точкой прецизионные ошибки, которые ухудшают производительность, если ячейкам разрешено делиться на слишком маленький размер в дереве).

Суть ускорения производительности с помощью пространственных индексов часто заключается в подобном акте балансировки. Например, мы обычно не хотим применять усеченный отбор к отдельным полигонам, отображаемым в компьютерной графике, потому что это, как правило, не только избыточно по сравнению с тем, что аппаратное обеспечение уже делает на этапе отсечения, но также слишком «мелко / детально» и требует слишком много большая обработка сама по себе по сравнению со временем, требуемым для запроса одного или нескольких полигонов. Но мы могли бы net значительно улучшить производительность, сделав что-то более "грубое", например, применить отбраковку усеченного конуса для всего существа или космического корабля (всей модели / меня sh), что позволит нам избежать запроса на рендеринг сразу многих полигонов с одного теста. Поэтому я часто использую термины «грубый» и «точный / гранулированный» часто в подобных дискуссиях (пока не найду лучшую терминологию, понятную большему количеству людей).

Унифицированная или адаптивная сетка

Вы можете представить себе квадродерево как «адаптивную» сетку с иерархическими размерами адаптивных ячеек сетки, работающими от «грубого» к «точному», когда мы переходим от root к листу единая интеллектуальная и адаптивная структура данных), в отличие от простой «однородной» сетки NxM.

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

Еще одна причина, по которой я склоняюсь к более простым, более непрерывным структурам данных во многих контекстах, заключается в том, что требования к производительности моделирования в реальном времени часто не только быстрая частота кадров, но согласованная и предсказуемая частота кадров. Последовательность важна, потому что даже если видеоигра имеет очень высокую частоту кадров для большей части игры, но некоторая часть игры приводит к значительному падению частоты кадров даже в течение короткого периода времени, игрок может d ie и играть в результате этого. В моем случае часто было очень важно избегать этих типов сценариев ios и иметь структуры данных, в которых в основном отсутствовал патологический сценарий производительности наихудшего случая ios. В целом, мне проще предсказать характеристики производительности множества более простых структур данных, которые не включают адаптивную иерархию и являются своего рода «тупыми». Очень часто я обнаруживаю, что согласованность и предсказуемость частоты кадров примерно связаны с тем, насколько легко я могу предсказать общее использование памяти структурой данных и насколько она стабильна. Если использование памяти крайне непредсказуемо и sporadi c, я часто (не всегда, но часто) нахожу, что частота кадров также будет sporadi c.

Так что я часто нахожу лучшие результаты, используя лично для сеток, но если сложно определить один оптимальный размер ячейки, который будет использоваться для сетки в конкретном контексте моделирования, я просто использую их несколько экземпляров: один экземпляр с большими размерами ячеек для «грубого» поиска (скажем, 10x10), один с меньшими для «более точных» поисков (скажем, 100x100), и, возможно, даже с меньшими ячейками для «лучших» поисков (скажем, 1000x1000). Если в результате грубого поиска не будет найдено результатов, более точные поиски не будут выполнены. Таким образом, я получаю некоторый баланс преимуществ четырех веток и сеток.

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

"Свободная двойная сетка", как я ее называю в одном из ответов на этот вопрос, является расширением этого мульти Идея Разница заключается в том, что более мелкая сетка фактически свободна и имеет размеры ячеек, которые увеличиваются и уменьшаются в зависимости от вставленных в нее элементов, всегда гарантируя, что один элемент, независимо от его размера или размера, должен быть вставлен только в одну ячейку в сетке. , Более грубая сетка хранит занятые ячейки более мелкой сетки, что приводит к двум запросам с постоянным временем (один в более грубой жесткой сетке, другой в более тонкую свободную сетку), чтобы вернуть список элементов потенциальных кандидатов, соответствующих параметру поиска. Он также имеет наиболее стабильное и предсказуемое использование памяти (не обязательно минимальное использование памяти, потому что для тонких / свободных ячеек требуется сохранение выровненного по оси ограничивающего прямоугольника, который расширяется / сжимается, что добавляет в ячейку еще примерно 16 байтов) и соответствующий стабильный кадр скорость, потому что один элемент всегда вставляется в одну ячейку и не требует никакой дополнительной памяти, необходимой для его хранения, кроме собственных данных элемента, за исключением случаев, когда его вставка приводит к расширению свободной ячейки до точки, в которую она должна быть вставлена дополнительные ячейки в более грубой сетке (что должно быть довольно редким сценарием).

несколько сеток для других целей

Я немного озадачен потому что я бы интуитивно использовал один, или, возможно, 3 std :: map с соответствующим оператором (), чтобы уменьшить объем памяти, но я не уверен, что это будет так быстро, так как запрос AABB будет означать сложение нескольких обращений, которые O (log n).

Я думаю, что это интуиция многих у нас есть, а также, возможно, и подсознательное желание просто опираться на одно решение для всего, потому что программирование может стать довольно утомительным и повторяющимся, и было бы идеально просто реализовать что-то один раз и повторно использовать его для всего: «один размер подходит» все "футболка. Но рубашка одного размера подходит для всех наших очень широких и мускулистых тел программистов *. Поэтому иногда полезно использовать аналогию с маленьким, средним и большим размерами.

  • Это очень плохая попытка юмора с моей стороны, чтобы сделать мои скучные тексты менее скучно читать.

Например, если вы используете std::map для чего-то вроде пространственного ха sh, тогда может быть много мыслей и возни с попытками найти оптимальный размер ячейки. В видеоигре можно пойти на компромисс с чем-то вроде создания размера ячейки относительно размера вашего обычного человека в игре (возможно, немного меньше или больше), так как многие модели / спрайты в игре могут быть предназначены для человека использовать. Но это может стать очень рискованным и очень неоптимальным для маленьких вещей и очень неоптимальным для гигантских c вещей. В этом случае мы могли бы преуспеть в том, чтобы сопротивляться нашей интуиции и желаниям просто использовать одно решение и использовать несколько (это мог бы быть все тот же код, но только несколько экземпляров одного и того же экземпляра класса для структуры данных, созданной с различными параметрами).

Что касается затрат на поиск по нескольким структурам данных вместо одной, то это лучше всего измерить, и стоит помнить, что в результате размеры ввода каждого контейнера будут меньше, что снижает стоимость каждого поиска и очень возможно улучшить местность ссылки. Это может превышать преимущества в иерархической структуре, которая требует логарифмического c времени поиска, например std::map (или нет, лучше всего просто измерить и сравнить), но я склонен использовать больше структур данных, которые делают это в постоянном времени (сетки) или ха sh таблицы). В моих случаях я обнаружил, что преимущества намного превышают дополнительные затраты, связанные с требованием множественного поиска для выполнения одного запроса, особенно когда размеры элементов радикально меняются, или я хочу что-то базовое c, напоминающее иерархию с двумя или более сетками NxM, которые варьируются от "грубого" до "точного".

Оптимизация на основе строк

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

Если мы храним один список для всей сетки затем мы должны постоянно вставлять и удалять из этого общего списка, когда элементы перемещаются, частицы рождаются и т. д. c. Это может привести к увеличению количества выделений / освобождений в куче и сокращению контейнера, а также к увеличению среднего шага памяти для перехода от одного элемента в этом списке к следующему, что, как правило, приводит к большему количеству пропусков кэша в результате загрузки ненужных данных. в строку кэша. Также в наши дни у нас так много ядер, что наличие единого общего контейнера для всей сетки может снизить возможность параллельной обработки сетки (например: поиск одной строки при одновременной вставке в другую). Это также может привести к большему использованию памяти net для структуры, так как, если мы используем непрерывную последовательность, такую ​​как std::vector или ArrayList, они часто могут хранить в памяти вдвое больше элементов, необходимых для сокращения времени вставки в амортизированное постоянное время, сводя к минимуму необходимость перераспределения и копирования прежних элементов в линейном времени, сохраняя избыточную емкость.

Связывая отдельный контейнер среднего размера для строки сетки или столбца вместо гиганта c один для всей сетки, мы можем уменьшить эти затраты в некоторых случаях *.

  • Это тип вещей, которые вы определенно измеряете до и после, хотя, чтобы убедиться, что это действительно улучшает общий кадр скорости и, вероятно, попытка в ответ на первую попытку сохранить один список для всей сетки, обнаружив множество необязательных пропусков кэша в профилировщике.

Это может вызвать вопрос о том, почему мы не используйте отдельный контейнер для маленьких списков для каждой ячейки в сетка. Это балансирование. Если мы храним такое количество контейнеров (например, миллион экземпляров std::vector для сетки 1000x1000, в которой, возможно, хранится очень мало элементов или нет элементов), это позволило бы обеспечить максимальный параллелизм и минимизировать шаг, чтобы добраться от одного элемента в ячейке к следующему один в ячейке, но это может быть довольно взрывоопасно при использовании памяти и вносить много дополнительной обработки и накладных расходов кучи.

Особенно в моем случае мои лучшие сетки могут хранить миллион ячеек или больше, но я только требуется 4 байта на ячейку. Последовательность переменного размера для каждой ячейки обычно бывала, по крайней мере, примерно на 24 байта или более (обычно намного больше) на ячейку в 64-разрядных архитектурах для хранения данных контейнера (обычно указатель и пара дополнительных целых чисел или три указателя). поверх блока памяти, выделенного из кучи), но помимо этого, каждый отдельный элемент, вставленный в пустую ячейку, может потребовать выделения кучи и принудительного пропуска кэша и сбоя страницы, а также очень часто из-за отсутствия временной локализации. Поэтому я нахожу баланс и предпочтительное место одним контейнером списка на строку, как правило, среди моих наиболее измеренных реализаций.

Я использую то, что я называю «списком массивов с одиночной связью», чтобы хранить элементы в строке сетки и разрешать вставки и удаления в постоянное время, в то же время допуская некоторую степень пространственной локальности с множеством смежных элементов. Это можно описать так:

struct GridRow
{
     struct Node
     {
         // Element data
         ...

         // Stores the index into the 'nodes' array of the next element
         // in the cell or the next removed element. -1 indicates end of
         // list.
         int next = -1;
     };

     // Stores all the nodes in the row.
     std::vector<Node> nodes;

     // Stores the index of the first removed element or -1
     // if there are no removed elements.
     int free_element = -1;
};

Это объединяет некоторые преимущества связанного списка с использованием бесплатного распределителя списков, но без необходимости управлять отдельными реализациями распределителя и структуры данных, которые я считаю слишком общими c и громоздкий для моих целей. Кроме того, делая это таким образом, мы можем вдвое уменьшить размер указателя до 32-битного индекса массива на 64-битных архитектурах, что я считаю большим измерением выигрыша в моих случаях использования, когда требования к выравниванию данных элемента объединены с 32-битным индексом не требуются дополнительные 32-битные отступы для class/struct, что часто случается со мной, так как я часто использую 32-битные или меньшие целые числа и 32-битную одинарную точность с плавающей точкой или 16-разрядные полуплавающие числа.

Неортодокс?

По этому вопросу:

Является ли этот тип поиска по сетке распространенным?

Я не уверен! Я склонен немного бороться с терминологией, и мне придется просить у людей прощения и терпения в общении. Я начал программировать с раннего детства в 1980-х, до того, как inte rnet получил широкое распространение, поэтому я стал полагаться на то, что изобрел множество своих собственных методик и в результате использовал свою грубую терминологию. Я получил степень в области компьютерных наук примерно полтора десятилетия спустя, когда мне исполнилось 20 лет, и я исправил некоторые терминологии и заблуждения, но у меня было много лет, я просто катал свои собственные решения. Поэтому я часто не уверен, сталкивались ли другие люди с такими же решениями или нет, и есть ли для них формальные имена и термины.

Это затрудняет общение с другими программистами и очень расстраивает их. время от времени мы оба, и мне нужно много терпения, чтобы объяснить, что я имею в виду. На встречах я привык всегда показывать что-то с очень многообещающими результатами, что делает людей более терпеливыми с моими грубыми и многословными объяснениями того, как они работают. Они, как правило, дают моим идеям гораздо больше шансов, если я начну с показа результатов, но я часто очень расстраиваюсь из-за ортодоксальности и догматизма, которые могут преобладать в этой отрасли, которые иногда могут расставлять приоритеты в понятиях гораздо больше, чем в исполнении и реальных результатах. , Я прагматик в душе, поэтому я не думаю о том, "какая структура данных лучше?" Я думаю с точки зрения того, что мы можем эффективно реализовать лично, учитывая наши сильные и слабые стороны, и что является интуитивным и не интуитивным для нас, и я готов бесконечно идти на компромисс в отношении чистоты концепций в пользу более простого и менее проблематичного c выполнение. Мне просто нравятся хорошие, надежные решения, которые естественным образом выходят у нас из рук, независимо от того, насколько они ортодоксальны или неортодоксальны, но многие из моих методов могут быть неортодоксальными в результате (или нет, и мне, возможно, еще только предстоит найти людей, которые сделали те же вещи). Я нашел этот сайт полезным в редких случаях для нахождения сверстников, которые говорят: «О, я тоже так делал! Я нашел лучшие результаты, если мы сделаем это [...]», или кто-то указал на что-то вроде «Что то, что вы предлагаете, называется [...]. "

В условиях, критичных к производительности, я, как бы грубо говоря, позволил профилировщику придумать для меня структуру данных. То есть я придумаю какой-нибудь быстрый первый черновик (обычно очень ортодоксальный) и измерим его профилировщиком, и пусть результаты профилировщика дадут мне идеи для второго черновика, пока я не схожу с чем-то простым, производительным и соответствующим образом масштабируемым. для требований (которые могут стать довольно неортодоксальными по пути). Я очень рад отказаться от множества идей, так как считаю, что нам нужно отсеять много плохих идей в процессе исключения, чтобы придумать хорошую, поэтому я склонен перебирать множество реализаций и идей и пришел чтобы стать действительно быстро Prototyper (у меня есть психологическая склонность упорно полюбят решения я потратил много времени на, так прилавок, что я научился тратить абсолютное минимальное время на решении до тех пор, пока не очень, очень перспективный) .

Вы можете увидеть мою точную методологию в работе в самых ответах на этот вопрос, где я итеративно сходился через много профилирования и измерения в течение нескольких дней и прототипирования от довольно ортодоксального квадродерева до это неортодоксальное решение «неплотная двойная сетка», которое обрабатывало наибольшее количество агентов с наиболее стабильной частотой кадров и было, во всяком случае, для меня намного быстрее и проще в реализации, чем все структуры до него. Мне пришлось go проанализировать множество ортодоксальных решений и измерить их, чтобы сформировать окончательную идею для необычного свободного варианта. Я всегда начинаю и одобряю ортодоксальные решения и начинаю внутри коробки, потому что они хорошо документированы и понятны, и просто очень осторожно и робко выхожу наружу, но я часто оказываюсь немного нестандартной, когда требования круче достаточно. Мне не чужды самые крутые требования, поскольку в моей отрасли и в качестве довольно низкоуровневого типа, работающего на движках, способность обрабатывать больше данных с хорошей частотой кадров часто приводит не только к большей интерактивности для пользователя, но и позволяет художникам создавать более детальный контент с более высоким визуальным качеством, чем когда-либо прежде. Мы всегда стремимся к более высокому и более высокому визуальному качеству при хороших частотах кадров, и это часто сводится к сочетанию как производительности, так и возможных грубых приближений, когда это возможно. Это неизбежно приводит к некоторой степени неортодоксальности с множеством собственных решений, уникальных для конкретного движка, и каждый движок имеет свои уникальные сильные и слабые стороны, когда вы сравниваете что-то вроде CryEngine с Unreal Engine с Frostbite с Unity.

Например, у меня есть эта структура данных, которую я использовал с детства, и я не знаю ее названия. Это простая концепция, и это просто иерархический набор битов, который позволяет найти пересечения потенциально миллионов элементов за несколько итераций простой работы, а также обойти миллионы элементов, занимающих множество, всего за несколько итераций (меньше, чем требования линейного времени для прохождения всего в наборе только через саму структуру данных, которая возвращает диапазоны занятых элементов / битов набора вместо отдельных элементов / индексов битов). Но я понятия не имею, как его зовут, потому что это просто то, что я катал, и я никогда не сталкивался с кем-то, кто говорил об этом в информатике. Я склонен называть это «иерархическим набором битов». Первоначально я назвал это "редким битовым деревом", но это кажется немного многословным. Это совсем не очень умная концепция, и я не удивлюсь и не буду разочарован (на самом деле весьма счастлив), когда найду кого-то еще, кто откроет то же самое решение до меня, но я не найду людей, которые используют или говорят о нем никогда. Он просто расширяет преимущества обычного плоского набора битов в быстром нахождении установленных пересечений с помощью побитового ИЛИ и быстро перемещается по всем установленным битам, используя FFZ / FFS, но сокращая требования к линейному времени обоих до логарифмических c (с помощью логарифмической базы будучи числом намного большим, чем 2). enter image description here

В любом случае, я не удивлюсь, если некоторые из этих решений очень неортодоксальны, но также не удивлюсь, если они будут достаточно ортодоксальными, а я только что потерпел неудачу найти правильное название и терминологию для этих методов. Для меня привлекательность таких сайтов - это одинокий поиск кого-то, кто использовал подобные методы, и пытался найти подходящие имена и термины, чтобы они часто заканчивались разочарованием. Я также надеюсь улучшить свою способность объяснять их, хотя я всегда был таким плохим и многословным здесь. Я считаю, что использование картинок мне очень помогает, потому что я нахожу, что человеческий язык невероятно пронизан двусмысленностью. Мне также нравится преднамеренно неточный образный язык, который охватывает и отмечает неоднозначности, такие как метафора и аналогия, и юмористическая гипербола, но я не обнаружил, что программисты склонны так ценить из-за отсутствия точности. Но я никогда не считал, что точность настолько важна, пока мы можем передать мясистые вещи и то, что «круто» в идее, в то время как они могут сделать свои собственные интерпретации всего остального. Извиняюсь за все объяснение, но надеюсь, что это прояснит некоторые аспекты моей грубой терминологии и общей методологии, которую я использую для достижения этих методов. Английский sh также не является моим родным языком, так что он добавляет еще один слой свертки, где мне приходится как-то переводить свои мысли в английские sh слова и много с этим бороться.

...