Adyacencia hexagonal

28

Ejemplo de espiral hexagonal

La imagen de arriba muestra una cuadrícula hexagonal de hexágonos. A cada celda de la cuadrícula se le asigna un índice, comenzando desde el centro y girando en sentido antihorario como se muestra. Tenga en cuenta que la cuadrícula continuará indefinidamente: la imagen de arriba es simplemente la primera sección. El siguiente hexágono sería adyacente a 60 y 37.

Su tarea es determinar si dos celdas dadas en esta cuadrícula son adyacentes.

Escriba un programa o función que, dados dos índices de celda, imprima / devuelva un valor verdadero si las dos celdas son adyacentes, y un valor falso si no.

Si no está limitado por razones prácticas, su código debería funcionar teóricamente para entradas de cualquier tamaño.

Casos de prueba de verdad:

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

Casos de prueba de Falsey:

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

Este es el por lo que gana la respuesta más corta en bytes. Se alientan las explicaciones, incluso para los idiomas no esotéricos.

John Michael Law
fuente

Respuestas:

7

Elixir , 263 257 264 223 214 218 214 bytes

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

Pruébalo en línea!

versión sin golf

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) Devuelve la parte entera del número
  • rem(a,b) Devuelve el resto de a / b
  • cond do end Esto es equivalente a else if o cláusulas de mayúsculas y minúsculas en muchos idiomas imperativos

Explicación

get_ring (índice)

Calcula el "anillo" del índice.

Ejemplo: 1 para 1-6, 2 para 7-18, etc.

Esto solo se aplica si se edita el resultado floor. Los dígitos finales representan qué tan lejos está esa ficha alrededor del anillo.

inv_get_ring (anillo)

Calcula el inverso de get_ring(index).

ring_base (anillo)

Calcula el índice del primer mosaico en el anillo.

is_corner (índice)

Las esquinas son fichas que tienen tres fichas adajcent en el anillo exterior. El anillo más interno consiste completamente en esquinas.

Ejemplos: 21,24,27,30,33,36

is_last (índice)

Es cierto si este índice es el más alto en su anillo.

es_primero (índice)

Es cierto si este es el mosaico base del anillo.

Garuno
fuente
2
He editado la respuesta para incluir una solución al caso :)
Garuno
Seguí su versión de golf a través de las primeras iteraciones, pero luego pareció que cambió su enfoque. ¿Su versión actual de golf sigue siendo equivalente a la versión sin golf?
John Michael Law
¡Sí lo es! Acabo de enterarme de que puedes declarar variables en línea en Elixir. Esto me dio la capacidad de deshacerme de las funciones lambda al comienzo del código. Simplemente barajé las variables un poco, para que sea más eficiente.
Garuno
5

MATL , 47 45 44 43 41 bytes

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

Pruébalo en línea! O verificar todos los casos de prueba .

Como beneficio adicional, el código genera una espiral hexagonal que traza las posiciones de los centros celulares, que se pueden ver gráficamente en MATL Online modificando la última parte del código.

Explicación

Idea general    El código primero construye una espiral hexagonal suficientemente grande con un paso unitario. La espiral se define como un vector de números complejos que representan las posiciones de los centros celulares. La indexación en ese vector con los números de entrada y el cálculo de la diferencia absoluta da la distancia entre las dos celdas indicadas. Las celdas son adyacentes solo si el resultado es 1. Sin embargo, debido a imprecisiones de coma flotante, es necesario redondear antes de comparar con 1.

Construyendo la espiral    La espiral tendrá un número de "capas" igual a la suma de las dos entradas. Esto es (mucho) más grande de lo necesario y asegura que las celdas de entrada estarán presentes en la espiral.

Para construir la espiral, primero se calcula el número complejo j 2/3 (donde j es la unidad imaginaria). Elevar esto a los exponentes 1 a 6 proporciona un conjunto básico de desplazamientos, de modo que seguir esos desplazamientos en orden rastrearía un hexágono. Este hexágono formaría la capa más interna de la espiral, excepto que estaría "cerrada". En realidad, queremos que ese hexágono "crezca" en el último paso y luego trazamos un hexágono más grande, con el doble de puntos (alineados en grupos de dos), para formar la siguiente capa de la espiral; Ver ilustración aquí . La siguiente capa tendrá tres puntos tantos como la primera (en grupos de tres); ver aquí .

Para hacer esto, el quinto desplazamiento del conjunto básico (que apunta en la dirección sureste) se elige como el paso "creciente". La capa k comienza con ese paso, seguido de los primeros cinco pasos básicos repetidos k veces, seguido del sexto paso (dirección este) repetido k −1 veces. Con suerte, esto se vuelve más claro al mirar las dos figuras vinculadas anteriormente.

El vector resultante, incluidas todas las capas, representa los desplazamientos complejos que trazarían la espiral. La suma acumulativa da las coordenadas reales de los centros celulares.

Por último, la celda inicial, ubicada en 0, está unida al final de este vector. Esto se debe a que MATL usa una indexación modular basada en 1, y el índice 0 se refiere a la última entrada de una matriz.

Prueba de adyacencia    Se seleccionan las dos celdas dadas por los números de entrada, se restan sus coordenadas y el valor absoluto se redondea y se compara con 1.

Código comentado

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
Luis Mendo
fuente
¿Podría agregar una explicación?
Shaggy
@ Shaggy Agregué una explicación general. Avíseme si está claro (es difícil de explicar). Agregaré el código comentado más tarde
Luis Mendo
2

05AB1E (heredado) , 30 29 27 bytes

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

Pruébalo en línea!

Explicación del código:

α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
                                }

Explicación de las matemáticas:

"Perdí" alrededor de 5 horas haciendo este golf. En resumen, comencé a hacer un gráfico 2D de las entradas, y dibujé Xdonde estaban adyacentes entre sí. Entonces encontré un patrón. ¡Lo busqué en OEIS y bingo! Encontré esa secuencia y utilicé la fórmula dada en el sitio web.

krinistof
fuente
1

C (gcc) , 175 173 bytes

Gracias a Peter Taylor por atrapar un error.

Gracias a ceilingcat por -2 bytes. Ese operador ~ sigue siendo mi punto ciego principal.

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;}

Pruébalo en línea!

Este enfoque se centra en encontrar la fila y la columna de las dos celdas y compararlas; cualquier vecino no puede tener sus coordenadas correspondientes diferentes en 1. Moviéndose desde el centro hacia afuera, observamos que cada capa tiene 6 celdas más que la anterior. Esto significa que el "índice" más alto en cada capa L está en la forma 6 * (L * (L - 1) * (L - 2) ...), o C = 6 * (L 2 + L) / 2 , donde C es el número de celda "global". Al barajar las cosas, obtenemos L 2 + L - C / 3 = 0, lo que da retrocesos matemáticos en la escuela secundaria. A partir de eso, obtenemos la fórmula ceil (sqrt (1/4 + C / 3) + 0.5). Al conectar un índice de celda global, recibimos en qué capa se encuentra la celda.

Dado que la primera celda en cada capa es naturalmente una más alta que la más alta de la capa anterior, encontramos L inicio = (6 * (L - 1) 2 + (L - 1)) / 2, que se simplifica a 3 * (L 2 - L). De eso obtenemos el índice de capa L index = C - L start .

A continuación, vemos que cada capa se compone de seis secciones, cada una de longitud L. Comenzando en el noreste y en sentido antihorario, vemos que para las dos primeras secciones (1 <= L índice <= 2 * L) , obtenemos la columna a partir de L - L índice . La siguiente sección L * 2 <L index <= L * 3 tiene todas las celdas que comparten una sola columna -L. Las dos secciones siguientes son L * 3 <L index <= L * 5 con sus columnas de acuerdo con el índice L - L * 4. Y, por último, la sexta sección tiene sus celdas en la columna L. Podemos desplazar los límites superiores un paso a lo largo para guardar algunos bytes en el código.

Entonces, ¿qué pasa con las filas? Para reutilizar el código, giramos la cuadrícula de modo que la celda 44 esté recta Luego ejecutamos la misma lógica que para las columnas pero esta vez llamamos a los resultados "filas". Por supuesto, en lugar de girar una cuadrícula, simplemente caminamos 1/6 de vuelta alrededor.

gastropner
fuente
@PeterTaylor Buena captura, gracias!
Gastropner
1

Python 3, 150 bytes

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

Mi solución básicamente sigue la misma línea de pensamiento que la anterior de Luis Mendo. Si se escribe más legible, el código se explica por sí mismo:

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. La función hhace lo siguiente:
  2. La Lista L contendrá las posiciones (complejas) de cada número.
  3. i Es el número de anillo.
  4. En el ciclo while, se agrega un nuevo anillo en cada iteración. En lugar de averiguar cuántos anillos necesitamos, simplemente continuamos construyendo la lista hasta que sea lo suficientemente larga como para contener a + b, luego es ciertamente lo suficientemente largo como para contener cualquiera de ellos.
  5. la 'lista de anillo' les una concatenación de 6 listas de len (i) multiplicado por el vector de pasos, donde el vector de pasos es 1j ** (2/3) con cierta potencia. El rango no comienza en 0 sino en 4, lo que provoca una rotación de toda la cuadrícula. Esto me permite hacer:
  6. l[0]+=1 en la línea 6, que es el paso de un anillo al siguiente.
  7. L+=l concatena la lista completa y la lista de anillo.
  8. La Lista L contiene solo vectores de pasos, que aún deben sumarse (integrarse) para obtener una posición. ¡Una característica interesante aquí es que podemos sumar el corte del número más bajo al más alto para obtener su distancia! Debido a los errores de redondeo, el resultado no será exactamente 1, de ahí el .9 <... <1.1. Curiosamente, el caso cero h(0,0)o h (0,1) se atiende implícitamente, porque la suma de una lista vacía es cero. Si pudiera estar seguro de que a<b, es decir, los argumentos vendrían en orden creciente, podría eliminar otros 14 bytes reemplazándolos L[min(a,b):max(a,b)]con L[a:b], pero ¡ay!

PD: No sabía que este era un desafío tan antiguo, apareció en la parte superior hace unos días, y me fastidiaba desde :)

Hermen
fuente
¡Esta es una respuesta genial! No se preocupe por la respuesta tardía, realmente no tenemos ningún problema con eso aquí en PPCG.
Rɪᴋᴇʀ
0

Mathematica, 111 105 104 bytes

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&

Explicación:

r=Floor[(1+Sqrt[(4#-1)/3])/2]&define una función rque toma la entrada #y calcula la distancia (en número de celdas) a la celda 0. Hace esto explotando el patrón en las últimas celdas de cada distancia / anillo: 0 = 3 (0 ^ 2 + 0), 6 = 3 (1 ^ 2 + 1), 18 = 3 (2 ^ 2 + 2), 36 = 3 (3 ^ 2 + 3), ... e invirtiendo la fórmula para ese patrón. Tenga en cuenta que para la celda 0, en realidad toma el piso de (1/2) + i * (sqrt (3) / 6), que calcula por componentes para obtener 0 + 0 * i = 0.

Con rdefinido, r@#es el anillo para la celda #(dentro de la definición de otra función). #+3r@#-3(r@#)^2&no aparece exactamente en el código, pero toma el número de una celda y resta el número más alto de una celda en el siguiente anillo interno, de modo que da la respuesta a la pregunta "¿qué celda del anillo actual es esta?" Por ejemplo, la celda 9 es la tercera celda del anillo 2, por r[9]lo que generaría 2 y #+3r@#-3(r@#)^2&[9]generaría 3.

Lo que podemos hacer con la función anterior es usarla para encontrar el ángulo polar , el ángulo en sentido antihorario desde el rayo "celda 0, celda 17, celda 58" hasta la celda en cuestión. La última celda de cada anillo siempre está en un ángulo Pi / 6, y vamos alrededor de un anillo en incrementos de Pi / (3 * ring_number). Entonces, en teoría, necesitamos calcular algo como Pi / 6 + (which_cell_of_the_current_ring) * Pi / (3 * ring_number). Sin embargo, la rotación de la imagen no afecta nada, por lo que podemos descartar la parte Pi / 6 (para guardar 6 bytes). Combinando esto con la fórmula anterior y simplificando, obtenemosPi(#/(3r@#)+1-r@#)&

Desafortunadamente, esto no está definido para la celda 0 ya que su número de anillo es 0, por lo que debemos evitarlo. Una solución natural sería algo así t=If[#==0,0,Pi(#/(3r@#)+1-r@#)]&. Pero como no nos importa el ángulo de la celda 0 y porque r@#se repite, podemos guardar un byte aquí cont=Limit[Pi(#/(3x)+1-x),x->r@#]&

Ahora que tenemos el número de anillo y el ángulo, podemos encontrar la posición de una celda (centro) para poder comprobar la adyacencia. Encontrar la posición real es molesto porque los anillos son hexagonales, pero podemos simplemente pretender que los anillos son círculos perfectos para que tratemos el número de anillo como la distancia al centro de la celda 0. Esto no será un problema ya que la aproximación es bastante cerca. Usando la forma polar de un número complejo , podemos representar esta posición aproximada en el plano complejo con una función simple:p = r@#*Exp[I*t@#] &;

La distancia entre dos números complejos en el plano complejo viene dada por el valor absoluto de su diferencia, y luego podemos redondear el resultado para encargarnos de cualquier error de la aproximación, y verificar si es igual a 1. La función que finalmente ¿Este trabajo no tiene un nombre, pero es Round@Abs[p@#-p@#2]==1&.


Puede probarlo en línea en el sandbox de Wolfram Cloud pegando código como el siguiente y haciendo clic en Gear -> "Evaluar celda" o presionando Shift + Enter o el teclado numérico 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]

O para todos los casos de prueba:

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}}]]&
Marcas.
fuente