Найти в Дзене
Омнибус Тьюринга

Развёртки кубов (код на R)

Поскольку мы уже решили, что сначала мы найдём на обоих кубиках единицу ("1") и проверим, что на противоположных гранях находится одно и то же число, нам необходимо уметь определять грань, противоположную данной. Если мы мы писали на Python или JavaScript, мы бы использовали словари (список пар "ключ - значение"). В Python словарь (dict) чрезвычайно удобный и функциональный, а в JavaScript это вообще основной тип данных (объект = словарь). Но в R нет словарей, хотя список (list) может произвести обманчивое впечатление, будто может работать как словарь. Может, но крайне ограничено. Поэтому мы делаем просто функцию axisPairs(). Обращаю внимание, что в R не обязательно делать в функции return - это в принципе функциональный язык и функция всегда возвращает просто результат последнего выражения. Но я по привычке всегда делаю return (Рис_1). Будем использовать такие обозначения для сторон кубиков: "u" - верхняя (up), "d" - нижняя (down), "l" - левая (left), "r" - правая (right), "f" - пере

Поскольку мы уже решили, что сначала мы найдём на обоих кубиках единицу ("1") и проверим, что на противоположных гранях находится одно и то же число, нам необходимо уметь определять грань, противоположную данной. Если мы мы писали на Python или JavaScript, мы бы использовали словари (список пар "ключ - значение"). В Python словарь (dict) чрезвычайно удобный и функциональный, а в JavaScript это вообще основной тип данных (объект = словарь). Но в R нет словарей, хотя список (list) может произвести обманчивое впечатление, будто может работать как словарь. Может, но крайне ограничено.

Поэтому мы делаем просто функцию axisPairs(). Обращаю внимание, что в R не обязательно делать в функции return - это в принципе функциональный язык и функция всегда возвращает просто результат последнего выражения. Но я по привычке всегда делаю return (Рис_1).

Будем использовать такие обозначения для сторон кубиков: "u" - верхняя (up), "d" - нижняя (down), "l" - левая (left), "r" - правая (right), "f" - передняя (front), "b" - задняя (back).

Рис_1
Рис_1

Далее нам необходимо научиться вращать кубик. Опять же - будь у нас словари, это чрезвычайно сделать на основе словарей. Но в R приходится делать функцию (Рис_2а, Рис_2б).

Рис_2а
Рис_2а
Рис_2б
Рис_2б

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

Далее - зададим, наконец, наши исходные кубики (Рис_7):

Рис_7
Рис_7

Оператор c в R означает конкатенацию. Кубики заданы как "списки" R, но больше это похоже на struct старого доброго C\C++. (Про списки R стоит написать отдельную статью). Данный код предлагает несколько (специально мною подобранных) вариантов 2-го кубика, который надо проверить на совпадение с 1-м кубиком. Первые два варианта для cube2 должны выдавать отрицательный результат (кубики отличны от cube1), третий вариант выдает положительный результат (cube2 идентичен cube1).

Нам важно получать значение той или иной грани кубика, а также устанавливать новое значение этой грани. Это делают следующие функции (т.н. "геттер" и "сеттер" - Рис_3):

-5

В R обращение к элементам списка (в терминах С\С++ это записи в struct) происходит через знак доллара $. Рисунок ниже показывает как в R работает функция which().

-6

Обращаю внимание: в силу того, что R - функциональный язык, функция не может изменять объект переданный в функцию (и поэтому бессмысленно интересоваться - передается этот объект по ссылке или по значению, считайте, что по значению). Фактически, при манипуляциях с этим объектом внутри функции создается его копия. И чтобы получить измененный объект, функция должна в явном виде возвращать эту измененную копию. Неприятный сюрприз для адептов C++\C#\Java\JavaScript\Python , привыкших (как я) курочить объекты функциями )).

Обращаю внимание, что сеттер, для того чтобы изменить значение всего одной стороны кубика, создает полностью новый кубик, чтобы вернуть его. Это очень расточительно с точки зрения человека привыкшего к философии С++\Java\Python. Поэтому я вообще не стал далее использовать этот сеттер.

Для вращения кубика в функции cube.rotate() я создаю новый кубик, в котором назначаю новые значения всем сторонам, полученным на основе поворота кубика, переданного в функцию (Рис_4):

Рис_4
Рис_4

Нам понадобится функция matchAxisPair(), которое для данного кубика и данного значения находит значение на противоположной стороне от той, где находится это значение; а также функция cube.equals(), которая сравнивает два кубика строго по фиксированным сторонам (то есть, не используя никаких вращений) - Рис_5:

Рис_5
Рис_5

Еще я создал выделенную функцию cube.findSide() (Рис_6), которая находит сторону кубика, на которой находится выделенное значение. Фактически тот же самый код используется в функции cube.matchAxisPair(), и она не вызывает cube.findSide() - возможно, этот момент можно как-то оптимизировать.

Рис_6
Рис_6

Итак, у нас есть кубики cube1 и cube2, и по умолчанию они не эквивалентны (Рис_7).

Алгоритм, описанный в предыдущей статье, реализуется так (Рис_8 и Рис_9):

Рис_8
Рис_8
Рис_9
Рис_9

Всё ))