Рубін
Фон
Існує три родини звичайних багатогранників, що розширюються на нескінченні розміри:
симплекси, до складу яких входить тетраедр (я часто називаю їх тут гіпертетраедрами, хоча термін симплекс є більш правильним.) Їх символи шлафі мають форму {3,3,...,3,3}
n-кубів, членом яких є куб. Їх символи шлафі мають форму{4,3,...,3,3}
ортоплекси, до складу яких входить октаедр (я їх часто буду називати гипероктаедрами) Їх символи шлафі мають форму {3,3,...,3,4}
Є ще одне нескінченне сімейство правильних багатогранників, символу {m}
, двох двовимірних багатокутників, які можуть мати будь-яку кількість ребер m.
Окрім цього, існує лише п’ять інших особливих випадків регулярного багатогранника: 3-мірний ікосаедр {3,5}
та додекаедр {5,3}
; їх 4-мірні аналоги 600-клітинні {3,3,5}
та 120-клітинні {5,3,3}
; і ще один 4-мірний багатогранник, 24-елементний {3,4,3}
(чиїми найближчими аналогами у трьох вимірах є кубоктаедр та його подвійний ромбічний додекаедр.)
Основна функція
Нижче наведена основна polytope
функція, що інтерпретує символ шлафі. Він очікує масив чисел і повертає масив, що містить купу масивів таким чином:
Масив усіх вершин, кожна з яких виражається масивом координат n-елементів (де n - кількість вимірів)
Масив усіх ребер, кожен виражений у вигляді 2-елементів індексних вершин
Масив усіх граней, кожне виражене як m-елемент вершинних індексів (де m - кількість вершин на обличчя)
і так далі, відповідно до кількості розмірів.
Він сам обчислює 2d багатогранники, викликає функції для трьох нескінченних розмірних сімей та використовує таблиці пошуку для п’яти спеціальних випадків. Він розраховує знайти функції та таблиці, оголошені над ним.
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
}
Функції для сімейства тетраедрів, кубів та октаедрів
https://en.wikipedia.org/wiki/Simplex
https://en.wikipedia.org/wiki/5-cell (4d симплекс)
http://mathworld.wolfram.com/Simplex.html
Пояснення сім'ї тетраедрів - координати
n-мірний симплекс / гіпертетраедр має n + 1 балів. Дуже легко надати вершини n-мірного симплексу в n + 1 вимірах.
Таким чином (1,0,0),(0,1,0),(0,0,1)
описується 2d трикутник, вбудований у 3 виміри, та (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)
описується 3d тетраедр, вбудований у 4 виміри. Це легко перевірити, підтвердивши, що всі відстані між вершинами є sqrt (2).
В Інтернеті наведено різні складні алгоритми для пошуку вершин n-мірного симплексу в n-мірному просторі. Я знайшов надзвичайно простий у коментарях Вілла Джаді на цю відповідь /mathpro//a/38725 . Остання точка лежить на лінії p=q=...=x=y=z
на відстані sqrt (2) від інших. Таким чином, трикутник вище можна перетворити на тетраедр, додавши крапку в будь-яку (-1/3,-1/3,-1/3)
або (1,1,1)
. Ці 2 можливі значення координат для останньої точки задаються (1-(1+n)**0.5)/n
і(1+(1+n)**0.5)/n
Оскільки в питанні сказано, що розмір n-вершини не має значення, я вважаю за краще помножити на n і використовувати координати (n,0,0..0)
до (0..0,0,n)
кінцевої точки, (t,t,..,t,t)
де t = 1-(1+n)**0.5
для простоти.
Оскільки центр цього тетраедра знаходиться не біля початку, виправлення всіх координат повинно бути виконано лінією, s.map!{|j|j-((1-(1+n)**0.5)+n)/(1+n)}
яка знаходить, наскільки далеко центр від початку і віднімає його. Я зберігав це як окрему операцію. Однак я використав, s[i]+=n
де s[i]=n
б це робив, натякав на те, що коли масив ініціалізується s=[0]*n
ми, ми можемо замість цього внести правильне зміщення і зробити корекцію центрування на самому початку, а не в кінці.
Пояснення сім'ї тетраедрів - топологія графа
Графік симплекса - це повний графік: кожна вершина з’єднується рівно один раз з кожною іншою вершиною. Якщо у нас є n симплекс, ми можемо видалити будь-яку вершину, щоб дати симплекс n-1, аж до точки, де у нас є трикутник або навіть край.
Таким чином, ми маємо в каталозі всього 2 ** (n + 1) позицій, кожен представлений двійковим номером. Це коливається від усіх 0
s для небуття, від однієї 1
для вершини та двох 1
s для ребра, аж до всіх 1
s для повного багатогранника.
Ми встановлюємо масив порожніх масивів для зберігання елементів кожного розміру. Потім обводимо від нуля до (2 ** n + 1), щоб генерувати кожну з можливих підмножин вершин, і зберігаємо їх у масиві відповідно до розміру кожного підмножини.
Нас не цікавить щось менше, ніж ребро (вершина чи нуль), а також повний багатогранник (оскільки повний куб не вказаний у прикладі у запитанні), тому ми повертаємось, tg[2..n]
щоб видалити ці небажані елементи. Перш ніж повернутися, ми торкаємось [tv], що містить вершинні координати, на початок.
код
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]}
Пояснення сімейства кубів та октаедрів - координати
У n-куба є 2**n
вершини, кожна з яких представлена масивом n 1
s і -1
s (всі можливості дозволені.) Ітераціюємо через індекси 0
до 2**n-1
списку всіх вершин і будуємо масив для кожної вершини шляхом ітерації через біти індекс та додавання -1
або 1
до масиву (найменш значущий біт до найбільш значущого біта.) Таким чином, Бінарний 1101
стає 4d точкою [1,-1,1,1]
.
N-октаедр або n-ортоплекс має 2n
вершини з усіма координатами нульовими, крім однієї, яка є 1
або -1
. Порядок вершин у створеному масиві є [[1,0,0..],[-1,0,0..],[0,1,0..],[0,-1,0..],[0,0,1..],[0,0,-1..]...]
. Зауважимо, що як октаедр є дуалом куба, вершини октаедра визначаються центрами граней куба, який його оточує.
Пояснення сімейства кубів та октаедрів - топологія графа
Деяке натхнення було взято з боку гіперкубів, і той факт, що гіпероктаедр є подвійним гіперкубом.
Для n-куба є 3**n
елементи в каталог. Наприклад, 3 куб має 3**3
= 27 елементів. Це можна побачити, вивчивши кубик рубіка, який має 1 центр, 6 граней, 12 ребер та 8 вершин на загальну кількість 27. Ми повторюємо через -1,0 та -1 у всіх вимірах, визначаючи n-куб довжиною бічної сторони 2x2x2 .. і повернути всі вершини, які НЕ знаходяться на протилежній стороні куба. Таким чином, центральна точка куба повертає всі 2 ** n вершин, а переміщення однієї одиниці від центру вздовж будь-якої осі зменшує кількість вершин удвічі.
Як і у сімейства тетраедрів, ми починаємо з генерації порожнього масиву масивів і заселяємо його відповідно до кількості вершин на елемент. Зауважте, що оскільки кількість вершин змінюється на 2 ** n, коли ми проходимо через краї, грані, кубики тощо, ми використовуємо log2(s.size)+1
замість цього просто s.size
. Знову ж таки, нам доведеться видалити саму гіперкуб і всі елементи з менш ніж двома вершинами, перш ніж повернутися з функції.
Сімейство октаедрів / ортоплексив є дуалами сімейства кубів, тому знову є 3**n
предмети в каталог. Тут ми повторюємо -1,0,1
всі розміри, і якщо ненулева координата вершини дорівнює відповідній координаті точки, вершина додається до списку, відповідного цій точці. Таким чином, ребро відповідає точці з двома ненульовими координатами, трикутником до точки з 3 ненульовими координатами і тетраедром до точки з 4 ненульовими контактами (у 4d просторі.)
Отримані масиви вершин для кожної точки зберігаються у великому масиві, як і для інших випадків, і перед поверненням ми повинні видалити будь-які елементи, що мають менше 2 вершин. Але в цьому випадку нам не доведеться видаляти повний батьківський n-tope, оскільки алгоритм його не записує.
Реалізація коду для куба була розрахована на максимальну схожість. Хоча це має певну елегантність, цілком ймовірно, що можна було б розробити ефективніші алгоритми, засновані на тих же принципах.
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
Код для генерації таблиць для 3-х спеціальних випадків
Була використана орієнтація на ікосаедр / додекаедр, орієнтовану на вісь п’ятикратної симетрії, паралельну останньому розміру, як це робиться для найбільш послідовного маркування деталей. Нумерація вершин і граней ікосаедра наведена на діаграмі в коментарях до коду та зворотна для додекаедра.
Відповідно до https://en.wikipedia.org/wiki/Regular_icosahedron широта 10 неполярних вершин ікосаедра становить +/- arctan (1/2) Координати перших 10 вершин ікосаедра обчислюються від це на двох колах радіусом 2 на відстані +/- 2 від площини xy. Це робить загальний радіус кругообігу sqrt (5), тому останні 2 вершини знаходяться в (0,0, + / - sqrt (2)).
Координати вершин додекаедра обчислюються шляхом підсумовування координат трьох вершин ікосаедра, які їх оточують.
=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]]
Код для генерації таблиць для 4d особливих випадків
Це трохи хак. Цей код займає кілька секунд. Було б краще зберегти вихід у файл і завантажити його у міру необхідності.
Список 120 вершинних координат для 600cell знаходиться з http://mathworld.wolfram.com/600-Cell.html . 24 координати вершин, які не мають золотистого відношення, утворюють вершини 24-комірки. У Вікіпедії є та ж схема, але є помилка у відносному масштабі цих 24 координат та інших 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
Приклад використання та виведення
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]