Rubí
Antecedentes
Hay tres familias de politopo regular que se extienden a dimensiones infinitas:
los símplex, de los cuales el tetraedro es miembro (a menudo me referiré a ellos aquí como hipertetraedros, aunque el término simplex es más correcto). Sus símbolos schlafi son de la forma {3,3,...,3,3}
los n-cubos, de los cuales el cubo es miembro. Sus símbolos schlafi son de la forma{4,3,...,3,3}
los ortoplexes, de los cuales es miembro el octaedro (a menudo me referiré a ellos aquí como hiperoctaedros) Sus símbolos schlafi son de la forma {3,3,...,3,4}
Hay otra familia infinita de politopes regulares, símbolo {m}
, el de los polígonos bidimensionales, que pueden tener cualquier número de aristas m.
Además de esto, solo hay otros cinco casos especiales de politopo regular: el icosaedro tridimensional {3,5}
y el dodecaedro {5,3}
; sus análogos de 4 dimensiones, de 600 celdas {3,3,5}
y 120 celdas {5,3,3}
; y otro politopo de 4 dimensiones, el de 24 celdas {3,4,3}
(cuyos análogos más cercanos en 3 dimensiones son el cuboctaedro y su doble el dodecaedro rómbico).
Función principal
A continuación se muestra la polytope
función principal que interpreta el símbolo schlafi. Espera una matriz de números y devuelve una matriz que contiene un montón de matrices de la siguiente manera:
Una matriz de todos los vértices, cada uno expresado como una matriz de coordenadas de n elementos (donde n es el número de dimensiones)
Una matriz de todos los bordes, cada uno expresado como un índice de vértices de 2 elementos
Una matriz de todas las caras, cada una expresada como un elemento m de índices de vértices (donde m es el número de vértices por cara)
y así sucesivamente según el número de dimensiones.
Calcula los politopos 2D en sí, llama a funciones para las 3 familias de dimensiones infinitas y utiliza tablas de búsqueda para los cinco casos especiales. Espera encontrar las funciones y tablas declaradas arriba.
include Math
#code in subsequent sections of this answer should be inserted here
polytope=->schl{
if schl.size==1 #if a single digit calculate and return a polygon
return [(1..schl[0]).map{|i|[sin(PI*2*i/schl[0]),cos(PI*2*i/schl[0])]},(1..schl[0]).map{|i|[i%schl[0],(i+1)%schl[0]]}]
elsif i=[[3,5],[5,3]].index(schl) #if a 3d special, lookup from tables
return [[vv,ee,ff],[uu,aa,bb]][i]
elsif i=[[3,3,5],[5,3,3],[3,4,3]].index(schl) #if a 4d special. lookup fromm tables
return [[v,e,f,g],[u,x,y,z],[o,p,q,r]][i]
elsif schl.size==schl.count(3) #if all threes, call tetr for a hypertetrahedron
return tetr[schl.size+1]
elsif schl.size-1==schl.count(3) #if all except one number 3
return cube[schl.size+1] if schl[0]==4 #and the 1st digit is 4, call cube for a hypercube
return octa[schl.size+1] if schl[-1]==4 #and the last digit is 4, call octa for a hyperoctahedron
end
return "error" #in any other case return an error
}
Funciones para las familias de tetraedro, cubo y octaedro
https://en.wikipedia.org/wiki/Simplex
https://en.wikipedia.org/wiki/5-cell (4d simplex)
http://mathworld.wolfram.com/Simplex.html
Explicación de la familia del tetraedro - coordenadas
un simplex / hipertetraedro n-dimensional tiene n + 1 puntos. Es muy fácil dar los vértices del simplex n-dimensional en n + 1 dimensiones.
Por lo tanto, (1,0,0),(0,1,0),(0,0,1)
describe un triángulo 2D incrustado en 3 dimensiones y (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)
describe un tetraedro 3d incrustado en 4 dimensiones. Esto se verifica fácilmente al confirmar que todas las distancias entre vértices son sqrt (2).
Se proporcionan varios algoritmos complicados en Internet para encontrar los vértices para el símplex n-dimensional en el espacio n-dimensional. Encontré una notablemente simple en los comentarios de Will Jagy sobre esta respuesta /mathpro//a/38725 . El último punto se encuentra en la línea p=q=...=x=y=z
a una distancia de sqrt (2) de los demás. Así, el triángulo de arriba se puede convertir en un tetraedro mediante la adición de un punto en (-1/3,-1/3,-1/3)
o (1,1,1)
. Estos 2 valores posibles de las coordenadas para el último punto están dados por (1-(1+n)**0.5)/n
y(1+(1+n)**0.5)/n
Como la pregunta dice que el tamaño del n-tope no importa, prefiero multiplicarlo por n y usar las coordenadas (n,0,0..0)
hasta (0..0,0,n)
con el punto final en (t,t,..,t,t)
donde t = 1-(1+n)**0.5
por simplicidad.
Como el centro de este tetraedro no está en el origen, la línea debe hacer una corrección a todas las coordenadas s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}
que encuentra qué tan lejos está el centro del origen y lo resta. He mantenido esto como una operación separada. Sin embargo, he utilizado s[i]+=n
where s[i]=n
would would, para aludir al hecho de que cuando la matriz se inicializa s=[0]*n
, podríamos colocar el desplazamiento correcto aquí y hacer la corrección de centrado desde el principio en lugar del final.
Explicación de la familia del tetraedro - topología gráfica
El gráfico del simplex es el gráfico completo: cada vértice está conectado exactamente una vez a todos los demás vértices. Si tenemos un n simplex, podemos eliminar cualquier vértice para dar un n-1 simplex, hasta el punto donde tenemos un triángulo o incluso un borde.
Por lo tanto, tenemos un total de 2 ** (n + 1) artículos para catalogar, cada uno representado por un número binario. Esto va desde todos los 0
s para nada, pasando por uno 1
para un vértice y dos 1
s para un borde, hasta todos los 1
s para el politopo completo.
Configuramos una matriz de matrices vacías para almacenar los elementos de cada tamaño. Luego hacemos un bucle de cero a (2 ** n + 1) para generar cada uno de los posibles subconjuntos de vértices y almacenarlos en la matriz de acuerdo con el tamaño de cada subconjunto.
No estamos interesados en nada más pequeño que un borde (un vértice o un cero) ni en el politopo completo (ya que el cubo completo no se da en el ejemplo de la pregunta), por lo que volvemos tg[2..n]
a eliminar estos elementos no deseados. Antes de regresar, añadimos [tv] que contiene las coordenadas del vértice al comienzo.
código
tetr=->n{
#Tetrahedron Family Vertices
tv=(0..n).map{|i|
s=[0]*n
if i==n
s.map!{(1-(1+n)**0.5)}
else
s[i]+=n
end
s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}
s}
#Tetrahedron Family Graph
tg=(0..n+1).map{[]}
(2**(n+1)).times{|i|
s=[]
(n+1).times{|j|s<<j if i>>j&1==1}
tg[s.size]<<s
}
return [tv]+tg[2..n]}
cube=->n{
#Cube Family Vertices
cv=(0..2**n-1).map{|i|s=[];n.times{|j|s<<(i>>j&1)*2-1};s}
#Cube Family Graph
cg=(0..n+1).map{[]}
(3**n).times{|i| #for each point
s=[]
cv.size.times{|j| #and each vertex
t=true #assume vertex goes with point
n.times{|k| #and each pair of opposite sides
t&&= (i/(3**k)%3-1)*cv[j][k]!=-1 #if the vertex has kingsmove distance >1 from point it does not belong
}
s<<j if t #add the vertex if it belongs
}
cg[log2(s.size)+1]<<s if s.size > 0
}
return [cv]+cg[2..n]}
octa=->n{
#Octahedron Family Vertices
ov=(0..n*2-1).map{|i|s=[0]*n;s[i/2]=(-1)**i;s}
#Octahedron Family Graph
og=(0..n).map{[]}
(3**n).times{|i| #for each point
s=[]
ov.size.times{|j| #and each vertex
n.times{|k| #and each pair of opposite sides
s<<j if (i/(3**k)%3-1)*ov[j][k]==1 #if the vertex is located in the side corresponding to the point, add the vertex to the list
}
}
og[s.size]<<s
}
return [ov]+og[2..n]}
explicación de familias de cubo y octaedro - coordenadas
El n-cubo tiene 2**n
vértices, cada uno representado por una matriz de n 1
s y -1
s (se permiten todas las posibilidades.) Nos iterar a través de los índices 0
a 2**n-1
de la lista de todos los vértices, y construir una matriz para cada vértice por iteración a través de los bits de la indexar y agregar -1
o 1
al conjunto (bit menos significativo a bit más significativo). De este modo, Binary se 1101
convierte en el punto 4d [1,-1,1,1]
.
El n-octaedro o n-orthoplex tiene 2n
vértices, con todas las coordenadas cero, excepto una, que puede ser 1
o -1
. El orden de los vértices en la matriz generada es [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]
. Tenga en cuenta que como el octaedro es el doble del cubo, los vértices del octaedro están definidos por los centros de las caras del cubo que lo rodea.
explicación de las familias de cubos y octaedros - topología gráfica
Se tomó algo de inspiración de los lados del hipercubo y del hecho de que el hiperoctaedro es el doble del hipercubo.
Para el n-cubo, hay 3**n
artículos para catalogar. Por ejemplo, el cubo 3 tiene 3**3
= 27 elementos. Esto se puede ver estudiando un cubo de rubik, que tiene 1 centro, 6 caras, 12 aristas y 8 vértices para un total de 27. iteramos a través de -1,0 y -1 en todas las dimensiones que definen un n-cubo de longitud lateral 2x2x2 .. y devuelve todos los vértices que NO están en el lado opuesto del cubo. Por lo tanto, el punto central del cubo devuelve todos los vértices de 2 ** n, y alejarse una unidad del centro a lo largo de cualquier eje reduce el número de vértices a la mitad.
Al igual que con la familia del tetraedro, comenzamos generando una matriz vacía de matrices y la llenamos de acuerdo con el número de vértices por elemento. Tenga en cuenta que debido a que el número de vértices varía como 2 ** n a medida que avanzamos a través de bordes, caras, cubos, etc., usamos en log2(s.size)+1
lugar de simplemente s.size
. Nuevamente, tenemos que eliminar el hipercubo en sí y todos los elementos con menos de 2 vértices antes de regresar de la función.
La familia octahedron / orthoplex son los duales de la familia de cubos, por lo tanto, nuevamente hay 3**n
artículos para catalogar. Aquí iteramos -1,0,1
para todas las dimensiones y si la coordenada distinta de cero de un vértice es igual a la coordenada correspondiente del punto, el vértice se agrega a la lista correspondiente a ese punto. Por lo tanto, un borde corresponde a un punto con dos coordenadas distintas de cero, un triángulo a un punto con 3 coordenadas distintas de cero y un tetraedro a un punto con 4 contactos distintos de cero (en el espacio 4d).
Las matrices de vértices resultantes para cada punto se almacenan en una matriz grande como en los otros casos, y tenemos que eliminar cualquier elemento con menos de 2 vértices antes de regresar. Pero en este caso no tenemos que eliminar el n-tope padre completo porque el algoritmo no lo registra.
Las implementaciones del código para el cubo fueron diseñadas para ser lo más similares posible. Si bien esto tiene una cierta elegancia, es probable que se puedan idear algoritmos más eficientes basados en los mismos principios.
https://en.wikipedia.org/wiki/Hypercube
http://mathworld.wolfram.com/Hypercube.html
https://en.wikipedia.org/wiki/Cross-polytope
http://mathworld.wolfram.com/CrossPolytope.html
Código para generar tablas para los casos especiales 3d
Se utilizó una orientación con el icosaedro / dodecaedro orientado con el eje de simetría quíntuple paralelo a la última dimensión, ya que contribuyó al etiquetado más consistente de las partes. La numeración de vértices y caras para el icosaedro se realiza según el diagrama en los comentarios del código, y se invierte para el dodecaedro.
De acuerdo con https://en.wikipedia.org/wiki/Regular_icosahedron, la latitud de los 10 vértices no polares del icosaedro es +/- arctan (1/2) Las coordenadas de los primeros 10 vértices del icosaedro se calculan a partir de esto, en dos círculos de radio 2 a una distancia +/- 2 del plano xy. Esto hace que el radio general de la circunsfera sea sqrt (5), por lo que los últimos 2 vértices están en (0,0, + / - sqrt (2)).
Las coordenadas de los vértices del dodecaedro se calculan sumando las coordenadas de los tres vértices de icosaedro que los rodean.
=begin
TABLE NAMES vertices edges faces
icosahedron vv ee ff
dodecahedron uu aa bb
10
/ \ / \ / \ / \ / \
/10 \ /12 \ /14 \ /16 \ /18 \
-----1-----3-----5-----7-----9
\ 0 / \ 2 / \ 4 / \ 6 / \ 8 / \
\ / 1 \ / 3 \ / 5 \ / 7 \ / 9 \
0-----2-----4-----6-----8-----
\11 / \13 / \15 / \17 / \19 /
\ / \ / \ / \ / \ /
11
=end
vv=[];ee=[];ff=[]
10.times{|i|
vv[i]=[2*sin(PI/5*i),2*cos(PI/5*i),(-1)**i]
ee[i]=[i,(i+1)%10];ee[i+10]=[i,(i+2)%10];ee[i+20]=[i,11-i%2]
ff[i]=[(i-1)%10,i,(i+1)%10];ff[i+10]=[(i-1)%10,10+i%2,(i+1)%10]
}
vv+=[[0,0,-5**0.5],[0,0,5**0.5]]
uu=[];aa=[];bb=[]
10.times{|i|
uu[i]=(0..2).map{|j|vv[ff[i][0]][j]+vv[ff[i][1]][j]+vv[ff[i][2]][j]}
uu[i+10]=(0..2).map{|j|vv[ff[i+10][0]][j]+vv[ff[i+10][1]][j]+vv[ff[i+10][2]][j]}
aa[i]=[i,(i+1)%10];aa[i+10]=[i,(i+10)%10];aa[i+20]=[(i-1)%10+10,(i+1)%10+10]
bb[i]=[(i-1)%10+10,(i-1)%10,i,(i+1)%10,(i+1)%10+10]
}
bb+=[[10,12,14,16,18],[11,13,15,17,19]]
Código para generar las tablas para los casos especiales 4d
Esto es un poco hack. Este código tarda unos segundos en ejecutarse. Sería mejor almacenar el resultado en un archivo y cargarlo según sea necesario.
La lista de 120 coordenadas de vértice para la celda 600 es de http://mathworld.wolfram.com/600-Cell.html . Las 24 coordenadas de vértice que no presentan una proporción áurea forman los vértices de una celda de 24. Wikipedia tiene el mismo esquema pero tiene un error en la escala relativa de estas 24 coordenadas y las otras 96.
#TABLE NAMES vertices edges faces cells
#600 cell (analogue of icosahedron) v e f g
#120 cell (analogue of dodecahedron) u x y z
#24 cell o p q r
#600-CELL
# 120 vertices of 600cell. First 24 are also vertices of 24-cell
v=[[2,0,0,0],[0,2,0,0],[0,0,2,0],[0,0,0,2],[-2,0,0,0],[0,-2,0,0],[0,0,-2,0],[0,0,0,-2]]+
(0..15).map{|j|[(-1)**(j/8),(-1)**(j/4),(-1)**(j/2),(-1)**j]}+
(0..95).map{|i|j=i/12
a,b,c,d=1.618*(-1)**(j/4),(-1)**(j/2),0.618*(-1)**j,0
h=[[a,b,c,d],[b,a,d,c],[c,d,a,b],[d,c,b,a]][i%12/3]
(i%3).times{h[0],h[1],h[2]=h[1],h[2],h[0]}
h}
#720 edges of 600cell. Identified by minimum distance of 2/phi between them
e=[]
120.times{|i|120.times{|j|
e<<[i,j] if i<j && ((v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2+(v[i][2]-v[j][2])**2+(v[i][3]-v[j][3])**2)**0.5<1.3
}}
#1200 faces of 600cell.
#If 2 edges share a common vertex and the other 2 vertices form an edge in the list, it is a valid triangle.
f=[]
720.times{|i|720.times{|j|
f<< [e[i][0],e[i][1],e[j][1]] if i<j && e[i][0]==e[j][0] && e.index([e[i][1],e[j][1]])
}}
#600 cells of 600cell.
#If 2 triangles share a common edge and the other 2 vertices form an edge in the list, it is a valid tetrahedron.
g=[]
1200.times{|i|1200.times{|j|
g<< [f[i][0],f[i][1],f[i][2],f[j][2]] if i<j && f[i][0]==f[j][0] && f[i][1]==f[j][1] && e.index([f[i][2],f[j][2]])
}}
#120 CELL (dual of 600 cell)
#600 vertices of 120cell, correspond to the centres of the cells of the 600cell
u=g.map{|i|s=[0,0,0,0];i.each{|j|4.times{|k|s[k]+=v[j][k]/4.0}};s}
#1200 edges of 120cell at centres of faces of 600-cell. Search for pairs of tetrahedra with common face
x=f.map{|i|s=[];600.times{|j|s<<j if i==(i & g[j])};s}
#720 pentagonal faces, surrounding edges of 600-cell. Search for sets of 5 tetrahedra with common edge
y=e.map{|i|s=[];600.times{|j|s<<j if i==(i & g[j])};s}
#120 dodecahedral cells surrounding vertices of 600-cell. Search for sets of 20 tetrahedra with common vertex
z=(0..119).map{|i|s=[];600.times{|j|s<<j if [i]==([i] & g[j])};s}
#24-CELL
#24 vertices, a subset of the 600cell
o=v[0..23]
#96 edges, length 2, found by minimum distances between vertices
p=[]
24.times{|i|24.times{|j|
p<<[i,j] if i<j && ((v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2+(v[i][2]-v[j][2])**2+(v[i][3]-v[j][3])**2)**0.5<2.1
}}
#96 triangles
#If 2 edges share a common vertex and the other 2 vertices form an edge in the list, it is a valid triangle.
q=[]
96.times{|i|96.times{|j|
q<< [p[i][0],p[i][1],p[j][1]] if i<j && p[i][0]==p[j][0] && p.index([p[i][1],p[j][1]])
}}
#24 cells. Calculates the centre of the cell and the 6 vertices nearest it
r=(0..23).map{|i|a,b=(-1)**i,(-1)**(i/2)
c=[[a,b,0,0],[a,0,b,0],[a,0,0,b],[0,a,b,0],[0,a,0,b],[0,0,a,b]][i/4]
s=[]
24.times{|j|t=v[j]
s<<j if (c[0]-t[0])**2+(c[1]-t[1])**2+(c[2]-t[2])**2+(c[3]-t[3])**2<=2
}
s}
https://en.wikipedia.org/wiki/600-cell
http://mathworld.wolfram.com/600-Cell.html
https://en.wikipedia.org/wiki/120-cell
http://mathworld.wolfram.com/120-Cell.html
https://en.wikipedia.org/wiki/24-cell
http://mathworld.wolfram.com/24-Cell.html
Ejemplo de uso y salida
cell24 = polytope[[3,4,3]]
puts "vertices"
cell24[0].each{|i|p i}
puts "edges"
cell24[1].each{|i|p i}
puts "faces"
cell24[2].each{|i|p i}
puts "cells"
cell24[3].each{|i|p i}
vertices
[2, 0, 0, 0]
[0, 2, 0, 0]
[0, 0, 2, 0]
[0, 0, 0, 2]
[-2, 0, 0, 0]
[0, -2, 0, 0]
[0, 0, -2, 0]
[0, 0, 0, -2]
[1, 1, 1, 1]
[1, 1, 1, -1]
[1, 1, -1, 1]
[1, 1, -1, -1]
[1, -1, 1, 1]
[1, -1, 1, -1]
[1, -1, -1, 1]
[1, -1, -1, -1]
[-1, 1, 1, 1]
[-1, 1, 1, -1]
[-1, 1, -1, 1]
[-1, 1, -1, -1]
[-1, -1, 1, 1]
[-1, -1, 1, -1]
[-1, -1, -1, 1]
[-1, -1, -1, -1]
edges
[0, 8]
[0, 9]
[0, 10]
[0, 11]
[0, 12]
[0, 13]
[0, 14]
[0, 15]
[1, 8]
[1, 9]
[1, 10]
[1, 11]
[1, 16]
[1, 17]
[1, 18]
[1, 19]
[2, 8]
[2, 9]
[2, 12]
[2, 13]
[2, 16]
[2, 17]
[2, 20]
[2, 21]
[3, 8]
[3, 10]
[3, 12]
[3, 14]
[3, 16]
[3, 18]
[3, 20]
[3, 22]
[4, 16]
[4, 17]
[4, 18]
[4, 19]
[4, 20]
[4, 21]
[4, 22]
[4, 23]
[5, 12]
[5, 13]
[5, 14]
[5, 15]
[5, 20]
[5, 21]
[5, 22]
[5, 23]
[6, 10]
[6, 11]
[6, 14]
[6, 15]
[6, 18]
[6, 19]
[6, 22]
[6, 23]
[7, 9]
[7, 11]
[7, 13]
[7, 15]
[7, 17]
[7, 19]
[7, 21]
[7, 23]
[8, 9]
[8, 10]
[8, 12]
[8, 16]
[9, 11]
[9, 13]
[9, 17]
[10, 11]
[10, 14]
[10, 18]
[11, 15]
[11, 19]
[12, 13]
[12, 14]
[12, 20]
[13, 15]
[13, 21]
[14, 15]
[14, 22]
[15, 23]
[16, 17]
[16, 18]
[16, 20]
[17, 19]
[17, 21]
[18, 19]
[18, 22]
[19, 23]
[20, 21]
[20, 22]
[21, 23]
[22, 23]
faces
[0, 8, 9]
[0, 8, 10]
[0, 8, 12]
[0, 9, 11]
[0, 9, 13]
[0, 10, 11]
[0, 10, 14]
[0, 11, 15]
[0, 12, 13]
[0, 12, 14]
[0, 13, 15]
[0, 14, 15]
[1, 8, 9]
[1, 8, 10]
[1, 8, 16]
[1, 9, 11]
[1, 9, 17]
[1, 10, 11]
[1, 10, 18]
[1, 11, 19]
[1, 16, 17]
[1, 16, 18]
[1, 17, 19]
[1, 18, 19]
[2, 8, 9]
[2, 8, 12]
[2, 8, 16]
[2, 9, 13]
[2, 9, 17]
[2, 12, 13]
[2, 12, 20]
[2, 13, 21]
[2, 16, 17]
[2, 16, 20]
[2, 17, 21]
[2, 20, 21]
[3, 8, 10]
[3, 8, 12]
[3, 8, 16]
[3, 10, 14]
[3, 10, 18]
[3, 12, 14]
[3, 12, 20]
[3, 14, 22]
[3, 16, 18]
[3, 16, 20]
[3, 18, 22]
[3, 20, 22]
[4, 16, 17]
[4, 16, 18]
[4, 16, 20]
[4, 17, 19]
[4, 17, 21]
[4, 18, 19]
[4, 18, 22]
[4, 19, 23]
[4, 20, 21]
[4, 20, 22]
[4, 21, 23]
[4, 22, 23]
[5, 12, 13]
[5, 12, 14]
[5, 12, 20]
[5, 13, 15]
[5, 13, 21]
[5, 14, 15]
[5, 14, 22]
[5, 15, 23]
[5, 20, 21]
[5, 20, 22]
[5, 21, 23]
[5, 22, 23]
[6, 10, 11]
[6, 10, 14]
[6, 10, 18]
[6, 11, 15]
[6, 11, 19]
[6, 14, 15]
[6, 14, 22]
[6, 15, 23]
[6, 18, 19]
[6, 18, 22]
[6, 19, 23]
[6, 22, 23]
[7, 9, 11]
[7, 9, 13]
[7, 9, 17]
[7, 11, 15]
[7, 11, 19]
[7, 13, 15]
[7, 13, 21]
[7, 15, 23]
[7, 17, 19]
[7, 17, 21]
[7, 19, 23]
[7, 21, 23]
cells
[0, 1, 8, 9, 10, 11]
[1, 4, 16, 17, 18, 19]
[0, 5, 12, 13, 14, 15]
[4, 5, 20, 21, 22, 23]
[0, 2, 8, 9, 12, 13]
[2, 4, 16, 17, 20, 21]
[0, 6, 10, 11, 14, 15]
[4, 6, 18, 19, 22, 23]
[0, 3, 8, 10, 12, 14]
[3, 4, 16, 18, 20, 22]
[0, 7, 9, 11, 13, 15]
[4, 7, 17, 19, 21, 23]
[1, 2, 8, 9, 16, 17]
[2, 5, 12, 13, 20, 21]
[1, 6, 10, 11, 18, 19]
[5, 6, 14, 15, 22, 23]
[1, 3, 8, 10, 16, 18]
[3, 5, 12, 14, 20, 22]
[1, 7, 9, 11, 17, 19]
[5, 7, 13, 15, 21, 23]
[2, 3, 8, 12, 16, 20]
[3, 6, 10, 14, 18, 22]
[2, 7, 9, 13, 17, 21]
[6, 7, 11, 15, 19, 23]