Гексагональная смежность

28

Пример Шестигранная Спираль

На изображении выше показана гексагональная сетка из шестиугольников. Каждой ячейке в сетке присваивается индекс, начиная с центра и вращаясь против часовой стрелки, как показано. Обратите внимание, что сетка будет продолжаться бесконечно - изображение выше - просто первый раздел. Следующий шестиугольник будет смежным с 60 и 37.

Ваша задача - определить, являются ли две заданные ячейки в этой сетке соседними.

Напишите программу или функцию, которая с учетом двух индексов ячеек печатает / возвращает истинное значение, если две ячейки смежны, и ложное значение, если нет.

Если это не ограничено практическими соображениями, ваш код теоретически должен работать для входных данных любого размера.

Правдивые тесты:

0, 1
7, 18
8, 22
24, 45
40, 64
64, 65

Контрольные примеры Falsey:

6, 57
29, 90
21, 38
38, 60
40, 63
41, 39
40, 40

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

Джон Майкл Лоу
источник

Ответы:

7

Эликсир , 263 257 264 223 214 218 214 байт

a=fn x,y->i=&(&1*(&1-1)*3+1)
[x,y]=Enum.sort [x,y]
if x<1,do: y in 1..6,else: (y-x==1||fn->a=y-trunc i.((r=(:math.sqrt(12*x-3)+3)/6)+1)
t=trunc r
a in [0,1,rem(b=x-i.(t)+1, t)<1&&b-t*6!=0&&2]||b<2&&a==-1 end.())end

Попробуйте онлайн!

негольфированная версия

def get_ring(x) do
    1/6*(:math.sqrt(12*x-3)+3)
end

def inv_get_ring(x), do: x*(x-1)*3+1

def ring_base(x), do: inv_get_ring(trunc(x))

def is_corner(x) do
    ring = trunc(get_ring(x))
    inv_ring = ring_base(ring)
    stuff = (x-inv_ring+1)
    rem(stuff, ring) == 0
end

def is_last(x),do: ring_base(get_ring(x)+1)-1 == x
def is_first(x),do: ring_base(get_ring(x)) == x

def hex_adj(x, y) do
    {x, y} = {min(x,y), max(x,y)}
    cond do 
        x == 0 ->y in 1..6      
        y-x==1 -> true
        true ->
            adj = trunc(inv_get_ring(get_ring(x)+1))
            number = if is_corner(x)&&!is_last(x), do: 2, else: 1
            if y-adj in 0..number do
                true
            else
                is_first(x) && y == adj-1
            end
    end
end
  • trunc(number) Возвращает целую часть числа
  • rem(a,b) Возвращает остаток от а / б
  • cond do end Это эквивалентно условию else if или switch во многих императивных языках

объяснение

get_ring (индекс)

Вычисляет «кольцо» индекса.

Пример: 1 для 1-6, 2 для 7-18 и т. Д.

Это применимо только если результат floored. Конечные цифры представляют, как далеко эта плитка находится вокруг кольца.

inv_get_ring (кольцо)

Рассчитывает обратное get_ring(index).

ring_base (кольцо)

Вычисляет индекс первой плитки в кольце.

is_corner (индекс)

Углы - это плитки с тремя прилегающими плитками во внешнем кольце. Самое внутреннее кольцо состоит полностью из углов.

Примеры: 21,24,27,30,33,36

is_last (индекс)

Истинно, если этот индекс самый высокий в своем кольце.

is_first (индекс)

Истинно, если это базовая плитка кольца.

Garuno
источник
2
Я отредактировал ответ, чтобы включить исправление в
крайнем
Я следил за вашей игрой в гольф через первые итерации, но потом казалось, что вы изменили свой подход. Ваша текущая версия для игры в гольф все еще эквивалентна версии без игры в гольф?
Джон Майкл Лоу
Да, это! Я только что узнал, что вы можете объявить переменные в Elixir. Это дало мне возможность избавиться от лямбда-функций в начале кода. Я просто немного перемешал переменные, чтобы сделать их более эффективными.
Гаруно
5

MATL , 47 45 44 43 41 байт

s:"JH3/^6:^t5)w5:&)@qY"w@Y"]vYs0hG)d|Yo1=

Попробуйте онлайн! Или проверьте все тестовые случаи .

В качестве бонуса код генерирует гексагональную спираль, которая отслеживает положения центров ячеек, что можно увидеть графически в MATL Online , изменив последнюю часть кода.

объяснение

Общая идея    Код сначала строит достаточно большую гексагональную спираль с шагом в единицу. Спираль определяется как вектор комплексных чисел, представляющих положения центров клеток. Индексирование в этот вектор с помощью входных чисел и вычисление абсолютной разницы дает расстояние между двумя указанными ячейками. Ячейки являются смежными, если и только если результат равен 1. Однако из-за неточностей с плавающей запятой, округление необходимо перед сравнением с 1.

Построение спирали    Спираль будет иметь количество «слоев», равное сумме двух входов. Это (намного) больше, чем необходимо, и гарантирует, что входные ячейки будут присутствовать в спирали.

Чтобы построить спираль, сначала вычисляется комплексное число j 2/3 (где j - мнимая единица). Повышение этого показателя до показателей 1–6 дает базовый набор смещений, так что следование за этими смещениями по порядку будет отслеживать шестиугольник. Этот шестиугольник будет образовывать самый внутренний слой спирали, за исключением того, что он будет «закрыт». На самом деле, мы хотим, чтобы этот шестиугольник «рос» на последнем шаге, а затем мы прослеживаем больший шестиугольник с вдвое большим количеством точек (выровненных в группы по две), чтобы сформировать следующий слой спирали; см. иллюстрацию здесь . Следующий слой будет иметь в три раза больше очков, чем первый (в группах по три); смотрите здесь .

Для этого в качестве «растущего» шага выбрано пятое смещение от базового набора (которое указывает в юго-восточном направлении). Уровень k начинается с этого шага, за которым следуют первые пять основных шагов, повторяемых k раз, а затем шестой шаг (в восточном направлении), повторяемый k -1 раз. Надеюсь, это станет яснее, если взглянуть на две цифры, связанные выше.

Результирующий вектор, включая все слои, представляет собой сложные смещения, которые будут отслеживать спираль. Накопленная сумма дает фактические координаты центров клеток.

Наконец, начальная ячейка, расположенная в 0, прикреплена к концу этого вектора. Это связано с тем, что MATL использует модульное индексирование на основе 1, а индекс 0 ссылается на эту последнюю запись массива.

Проверка на смежность    Две ячейки, заданные входными числами, выбираются, их координаты вычитаются, а абсолютное значение округляется и сравнивается с 1.

Код комментария

s         % Implicitly input array of two numbers. Push their sum, say S
:         % Range [1 2 ... S]
"         % For each k in [1 2 ... S]
  J       %   Push 1j
  H3/     %   Push 2, then 3, then divide: gives 2/3
  ^       %   Power
  6:      %   Push [1 2 ... 6]
  ^       %   Element-wise power. This is the array of 6 basic displacements
  t5)     %   Duplicate. Get 5th entry
  w5:&)   %   Swap. Push subarray with entries 1st to 5th, then push 6th
  @qY"    %   Repeat the 6th displacement k-1 times
  w@Y"    %   Swap. Repeat 1st to 5th displacements k times
]         % End
v         % Concatenate everything into a column vector
Ys        % Cumulative sum. This gives the cell center coordinates
0h        % Append a 0
G)        % Index by the input vector
d|        % Absolute difference
Yo        % Round to nearest integer
1=        % Does it equal 1? Implicitly display
Луис Мендо
источник
Не могли бы вы добавить объяснение?
Лохматый
@ Shaggy Я добавил общее объяснение. Дайте мне знать, если это понятно (это сложно объяснить). Я добавлю закомментированный код позже
Луис Мендо
2

05AB1E (наследие) , 30 29 27 байтов

α2‹i1q}1ݹ+v12y*3-tîÌy+}Ÿ²å

Попробуйте онлайн!

Объяснение кода:

α2‹i1q}                     : if the absolute diff of the two number is 1 or 0 return 1
                          ²å: return that the second number is in
                         Ÿ  : range of {
       1Ý                   :  create [0, 1]
         ¹+                 :  add the first number to the elements
           v            }   :  map that list
            12y*3-tîÌy+     :  calculate the corresponding value where it's an adjacency
                                }

Объяснение математики:

Я "впустую" около 5 часов, делая этот гольф. Короче говоря, я начал делать двухмерный график входов и рисовал Xтам, где они были смежны друг с другом. Тогда я нашел образец. Я искал это на OEIS и бинго! Я нашел эту последовательность и использовал формулу, приведенную на сайте.

krinistof
источник
1

C (gcc) , 175 173 байта

Спасибо Питеру Тейлору за обнаружение ошибки.

Благодаря потолку кошки за -2 байта. Этот оператор продолжает оставаться моей главной слепой точкой.

c,r,C,L;y(a){a=a<L*2?L-a:a<L*3?-L:a<5*L?a-L*4:L;}z(a){L=ceil(sqrt(a/3.+.25)-.5);C=y(a-=3*L*~-L);L?L=y((L+a)%(L*6)):0;}f(a,b){z(a);c=C,r=L;z(b);a=a-b&&(abs(c-C)|abs(r-L))<2;}

Попробуйте онлайн!

Этот подход ориентирован на поиск строки и столбца двух ячеек и сравнение их; любые соседи не могут иметь соответствующие координаты, отличающиеся более чем на 1. Перемещаясь от центра наружу, мы видим, что каждый слой имеет на 6 ячеек больше, чем предыдущий. Это означает, что самый высокий «индекс» в каждом слое L находится в форме 6 * (L * (L - 1) * (L - 2) ...), или C = 6 * (L 2 + L) / 2 где C - это «глобальный» номер ячейки. Перемешивая вещи, мы получаем L 2 + L - C / 3 = 0, что дает воспоминания о старшей школе по математике. Отсюда получаем формулу ceil (sqrt (1/4 + C / 3) + 0,5). Подключив к нему глобальный индекс ячейки, мы получим, в каком слое находится ячейка.

Поскольку первая ячейка в каждом слое, естественно, на одну высшую, чем самая высокая в предыдущем слое, мы находим L start = (6 * (L - 1) 2 + (L - 1)) / 2, что упрощается до 3 * (L 2 - л). Отсюда получаем индекс слоя L index = C - L start .

Далее мы видим, что каждый слой состоит из шести секций, каждая длиной L. Начиная с северо-востока и против часовой стрелки, мы видим, что для первых двух секций (1 <= L index <= 2 * L) мы получаем столбец из L - L индекса . В следующем разделе L * 2 <L index <= L * 3 все ячейки имеют общий столбец -L. Два следующих раздела - это L * 3 <L index <= L * 5 с их столбцами в соответствии с L index - L * 4. И, наконец, шестой раздел имеет все ячейки в столбце L. Мы можем сдвинуть верхние границы на один шаг вперед сохранить несколько байтов в коде.

Так что же насчет строк? Чтобы повторно использовать код, мы поворачиваем сетку так, чтобы ячейка 44 находилась прямо вверх. Затем мы запускаем ту же логику, что и для столбцов, но на этот раз называем результаты «строками». Конечно, вместо того, чтобы фактически поворачивать сетку, мы просто обходим ее на 1/6 круга.

gastropner
источник
@PeterTaylor Хороший улов, спасибо!
Гастропнер
1

Python 3, 150 байт

def h(a,b):
 L=[];i=1
 while len(L)<a+b:r=sum((i*[1j**(k/3)]for k in range(4,16,2)),[]);r[0]+=1;L+=r;i+=1
 return.9<abs(sum(L[min(a,b):max(a,b)]))<1.1

Мое решение в основном следует той же мысли, что и Луис Мендо выше. Если написано более читабельно, код довольно понятен:

def h(a,b):
    L=[]
    i=1
    while len(L)<a+b:
        l=sum((i*[1j**(k/3)]for k in range(4,16,2)),[])
        l[0]+=1
        L+=l
        i+=1
return .9<abs(sum(L[min(a,b):max(a,b)]))<1.1
  1. Функция hделает следующее:
  2. Список L будет содержать (сложные) позиции каждого числа.
  3. i это номер звонка.
  4. В цикле while новое кольцо добавляется при каждой итерации. Вместо того, чтобы выяснить, сколько колец нам нужно, мы просто продолжаем составлять список до тех пор, пока он не станет достаточно длинным, чтобы содержать a + b, тогда, конечно, он будет достаточно длинным, чтобы содержать любое из них.
  5. 'ring-list' lпредставляет собой объединение 6 списков len (i), умноженных на вектор шага, где вектор шага равен 1j ** (2/3) до некоторой степени. Диапазон начинается не с 0, а с 4, что вызывает вращение всей сетки. Это позволяет мне сделать:
  6. l[0]+=1 в строке 6, которая является шагом от одного кольца к следующему.
  7. L+=l объединяет полный список и список звонков.
  8. Список L содержит только векторы шагов, которые все еще должны быть суммированы (интегрированы), чтобы получить позицию. Отличная особенность в том, что мы можем просто суммировать срез от наименьшего числа до наибольшего, чтобы получить их расстояние! Из-за ошибок округления результат не будет точно равен 1, следовательно, 0,9 <... <1,1. Интересно, что нулевой случай h(0,0)или h (0,1) позаботился неявно, потому что сумма пустого списка равна нулю. Если бы я мог быть уверен a<b, что аргументы будут приходить в возрастающем порядке, я мог бы сбрить еще 14 байтов, заменив их L[min(a,b):max(a,b)]на L[a:b], но, увы!

PS: я не знал, что это был такой старый вызов, он появился на вершине несколько дней назад, и с тех пор продолжал давить на меня :)

Hermen
источник
Это отличный ответ! Не беспокойтесь о позднем ответе, у нас нет никаких проблем с этим здесь, на PPCG.
Rɪᴋᴇʀ
0

Mathematica, 111 105 104 байта

r=Floor[(1+Sqrt[(4#-1)/3])/2]&;t=Limit[Pi(#/(3x)+1-x),x->r@#]&;p=r@#*Exp[I*t@#]&;Round@Abs[p@#-p@#2]==1&

Объяснение:

r=Floor[(1+Sqrt[(4#-1)/3])/2]&определяет функцию, rкоторая принимает входные данные #и вычисляет расстояние (в количестве ячеек) до ячейки 0. Это делается путем использования шаблона в последних ячейках каждого расстояния / кольца: 0 = 3 (0 ^ 2 + 0), 6 = 3 (1 ^ 2 + 1), 18 = 3 (2 ^ 2 + 2), 36 = 3 (3 ^ 2 + 3), ... и инвертируя формулу для этого шаблона. Обратите внимание, что для ячейки 0 она фактически принимает значение (1/2) + i * (sqrt (3) / 6), которое вычисляется по компонентам, чтобы получить 0 + 0 * i = 0.

С rопределяется, r@#является кольцо для ячейки #(внутри определения другой функции). #+3r@#-3(r@#)^2&не появляется в коде точно, но он берет номер ячейки и вычитает наибольшее число ячеек в следующем внутреннем кольце, так что это дает ответ на вопрос "какая ячейка текущего кольца это?" Например, ячейка 9 - это третья ячейка кольца 2, поэтому r[9]выведите 2 и #+3r@#-3(r@#)^2&[9]выведите 3.

Что мы можем сделать с помощью функции выше, так это использовать ее для нахождения полярного угла , угла против часовой стрелки от луча «ячейка 0, ячейка 17, ячейка 58» к рассматриваемой ячейке. Последняя ячейка каждого кольца всегда находится под углом Pi / 6, и мы обходим кольцо с шагом Pi / (3 * ring_number). Итак, теоретически нам нужно вычислить что-то вроде Pi / 6 + (which_cell_of_the_current_ring) * Pi / (3 * ring_number). Однако поворот изображения ни на что не влияет, поэтому мы можем отбросить часть Pi / 6 (чтобы сохранить 6 байт). Объединяя это с предыдущей формулой и упрощая, мы получаемPi(#/(3r@#)+1-r@#)&

К сожалению, это не определено для ячейки 0, так как ее номер звонка равен 0, поэтому нам нужно обойти это. Естественное решение было бы что-то вроде t=If[#==0,0,Pi(#/(3r@#)+1-r@#)]&. Но так как нас не волнует угол для ячейки 0 и поскольку r@#он повторяется, мы можем сохранить здесь байт с помощьюt=Limit[Pi(#/(3x)+1-x),x->r@#]&

Теперь, когда у нас есть номер кольца и угол, мы можем найти положение ячейки (центра), чтобы мы могли проверить смежность. Поиск фактической позиции раздражает, потому что кольца гексагональные, но мы можем просто притвориться, что кольца являются идеальными кругами, так что мы рассматриваем число колец как расстояние до центра ячейки 0. Это не будет проблемой, так как приближение довольно близко. Используя полярную форму комплексного числа , мы можем представить это приблизительное положение в комплексной плоскости с помощью простой функции:p = r@#*Exp[I*t@#] &;

Расстояние между двумя комплексными числами на комплексной плоскости определяется абсолютной величиной их разности, и затем мы можем округлить результат, чтобы учесть любые ошибки из аппроксимации, и проверить, равно ли это 1. Функция, которая в конечном итоге У этой работы нет имени, но есть Round@Abs[p@#-p@#2]==1&.


Вы можете попробовать его онлайн в песочнице Wolfram Cloud , вставив код, подобный приведенному ниже, и щелкнув Gear -> «Оценить ячейку» или нажав Shift + Enter или цифровую клавишу Enter:

r=Floor[(1+Sqrt[(4#-1)/3])/2]&;t=Limit[Pi(#/(3x)+1-x),x->r@#]&;p=r@#*Exp[I*t@#]&;Round@Abs[p@#-p@#2]==1&[24,45]

Или для всех тестовых случаев:

r=Floor[(1+Sqrt[(4#-1)/3])/2]&;t=Limit[Pi(#/(3x)+1-x),x->r@#]&;p=r@#*Exp[I*t@#]&;Round@Abs[p@#-p@#2]==1&//MapThread[#,Transpose[{{0,1},{7,18},{8,22},{24,45},{40,64},{64,65},{6,57},{29,90},{21,38},{38,60},{40,63},{41,39},{40,40}}]]&
Метки.
источник