CJam, 189 187 byte
Questo sarà difficile da spiegare ... La complessità del tempo è garantita O(scary)
.
qi:N_3>{,aN*]N({{:L;N,X)-e!{X)_@+L@@t}%{X2+<z{_fe=:(:+}%:+!},}%:+}fX{:G;N3m*{_~{G@==}:F~F\1m>~F\F=}%:*},:L,({LX=LX)>1$f{\_@a\a+Ne!\f{\:M;~{M\f=z}2*\Mff==}:|{;}|}\a+}fX]:~$e`{0=1=},,}{!!}?
Se sei abbastanza coraggioso, provalo online . Sul mio maledetto laptop riesco a ottenere fino a 6 con l'interprete Java o 5 nell'interprete online.
Spiegazione
Non ho una grande preparazione in matematica (ho appena finito il liceo, ho iniziato a studiare in CS alla prossima settimana). Quindi abbi pazienza con me se commetto errori, dichiari l'ovvio o faccio le cose in modi orribilmente inefficaci.
Il mio approccio è una forza bruta, anche se ho cercato di renderlo un po 'più intelligente. I passaggi principali sono:
- Genera tutti i possibili operandi ∗ per un gruppo di ordine n (ovvero, enumera tutti i gruppi di ordine n );
- Genera tutte le possibili biiezioni φ tra due gruppi di ordine n ;
- Utilizzando i risultati dei passaggi 1 e 2, determinare tutti gli isomorfismi tra due gruppi di ordine n ;
- Utilizzando il risultato del passaggio 3, contare il numero di gruppi fino all'isomorfismo.
Prima di vedere come viene eseguito ogni passaggio, togliiamo un po 'di codice banale:
qi:N_ e# Get input as integer, store in N, make a copy
3>{...} ? e# If N > 3, do... (see below)
{!!} e# Else, push !!N (0 if N=0, 1 otherwise)
Il seguente algoritmo non funziona correttamente con n <4 , i casi da 0 a 3 vengono gestiti con una doppia negazione.
D'ora in poi, gli elementi di un gruppo saranno scritti come {1, a, b, c, ...} , dove 1 è l'elemento identità. Nell'implementazione di CJam, gli elementi corrispondenti sono {0, 1, 2, 3, ...} , dove 0 è l'elemento identità.
Iniziamo dal passaggio 1. Scrivere tutti i possibili operatori per un gruppo di ordini n equivale a generare tutte le tabelle n × n Cayley valide . La prima riga e colonna sono banali: sono entrambe {1, a, b, c, ...} (da sinistra a destra, su-giù).
e# N is on the stack (duplicated before the if)
,a e# Generate first row [0 1 2 3 ...] and wrap it in a list
N* e# Repeat row N times (placeholders for next rows)
] e# Wrap everything in a list
e# First column will be taken care of later
Sapere che un tavolo Cayley è anche un quadrato latino ridotto (a causa della proprietà di annullamento) consente di generare le possibili tabelle riga per riga. A partire dalla seconda riga (indice 1), generiamo tutte le permutazioni uniche per quella riga, mantenendo la prima colonna fissata sul valore dell'indice.
N({ }fX e# For X in [0 ... N-2]:
{ }% e# For each table in the list:
:L; e# Assign the table to L and pop it off the stack
N, e# Push [0 ... N-1]
X) e# Push X+1
- e# Remove X+1 from [0 ... N-1]
e! e# Generate all the unique permutations of this list
{ }% e# For each permutation:
X)_ e# Push two copies of X+1
@+ e# Prepend X+1 to the permutation
L@@t e# Store the permutation at index X+1 in L
{...}, e# Filter permutations (see below)
:+ e# Concatenate the generated tables to the table list
Naturalmente non tutte le permutazioni sono valide: ogni riga e colonna deve contenere tutti gli elementi esattamente una volta. A questo scopo viene utilizzato un blocco filtro (un valore di verità mantiene la permutazione, uno di falsa la rimuove):
X2+ e# Push X+2
< e# Slice the permutations to the first X+2 rows
z e# Transpose rows and columns
{ }% e# For each column:
_fe= e# Count occurences of each element
:( e# Subtract 1 from counts
:+ e# Sum counts together
:+ e# Sum counts from all columns together
! e# Negate count sum:
e# if the sum is 0 (no duplicates) the permutation is kept
e# if the sum is not zero the permutation is filtered away
Nota che sto filtrando all'interno del ciclo di generazione: questo rende il codice un po 'più lungo (rispetto a generazione e filtro distinti), ma migliora notevolmente le prestazioni. Il numero di permutazioni di un set di dimensioni n è n! , quindi la soluzione più breve richiederebbe molta memoria e tempo.
Un elenco di tabelle Cayley valide è un grande passo verso l'enumerazione degli operatori, ma essendo una struttura 2D, non può verificare l'associatività, che è una proprietà 3D. Quindi il prossimo passo è filtrare le funzioni non associative.
{ }, e# For each table, keep table if result is true:
:G; e# Store table in G, pop it off the stack
N3m* e# Generate triples [0 ... N-1]^3
{ }% e# For each triple [a b c]:
_~ e# Make a copy, unwrap top one
{ }:F e# Define function F(x,y):
G@== e# x∗y (using table G)
~F e# Push a∗(b∗c)
\1m> e# Rotate triple right by 1
~ e# Unwrap rotated triple
F\F e# Push (a∗b)∗c
= e# Push 1 if a∗(b∗c) == (a∗b)∗c (associative), 0 otherwise
:* e# Multiply all the results together
e# 1 (true) only if F was associative for every [a b c]
Accidenti! Molto lavoro, ma ora abbiamo elencato tutti i gruppi di ordine n (o meglio, le operazioni su di esso - ma il set è stato risolto, quindi è la stessa cosa). Passaggio successivo: trova gli isomorfismi. Un isomorfismo è una biiezione tra due di quei gruppi tale che φ (x ∗ y) = φ (x) ∗ φ (y) . Generare quelle biiezioni in CJam è banale: Ne!
lo farà. Come possiamo controllarli? La mia soluzione parte da due copie della tabella Cayley per x ∗ y . Su una copia, φ viene applicato a tutti gli elementi, senza toccare l'ordine di righe o colonne. Questo genera la tabella per φ (x ∗ y) . Dall'altro gli elementi sono lasciati così come sono, ma le righe e le colonne sono mappate attraverso φ . Cioè, la riga / colonnax diventa la riga / colonna φ (x) . Questo genera la tabella per φ (x) ∗ φ (y) . Ora che abbiamo i due tavoli, non ci resta che confrontarli: se sono uguali, abbiamo trovato un isomorfismo.
Naturalmente, dobbiamo anche generare le coppie di gruppi su cui testare l'isomorfismo. Abbiamo bisogno di tutte le 2 combinazioni dei gruppi. Sembra che CJam non abbia un operatore per le combinazioni. Possiamo generarli prendendo ogni gruppo e combinandolo solo con gli elementi che lo seguono nell'elenco. Curiosità: il numero di 2 combinazioni è n × (n - 1) / 2 , che è anche la somma dei primi n - 1 numeri naturali. Tali numeri sono chiamati numeri triangolari: prova l'algoritmo su carta, una riga per elemento fisso e vedrai perché.
:L e# List of groups is on stack, store in L
,( e# Push len(L)-1
{ }fX e# For X in [0 ... len(L)-2]:
LX= e# Push the group L[X]
LX)> e# Push a slice of L excluding the first X+1 elements
1$ e# Push a copy of L[X]
f{...} e# Pass each [L[X] Y] combination to ... (see below)
e# The block will give back a list of Y for isomorphic groups
\a+ e# Append L[X] to the isomorphic groups
] e# Wrap everything in a list
Il codice sopra risolve il primo elemento della coppia, L [X] , e lo combina con altri gruppi (chiamiamo ciascuno di quei Y ). Passa la coppia a un blocco di test di isomorfismo che mostrerò tra poco. Il blocco restituisce un elenco di valori di Y per cui L [X] è isomorfo a Y . Quindi L [X] viene aggiunto a questo elenco. Prima di capire perché le liste sono impostate in questo modo, diamo un'occhiata al test di isomorfismo:
\_@ e# Push a copy of Y
a\a+ e# L[X] Y -> [L[X] Y]
Ne! e# Generate all bijective mappings
\f{ } e# For each bijection ([L[X] Y] extra parameter):
\:M; e# Store the mapping in M, pop it off the stack
~ e# [L[X] Y] -> L[X] Y
{ }2* e# Repeat two times (on Y):
M\f= e# Map rows (or transposed columns)
z e# Transpose rows and columns
e# This generates φ(x) ∗ φ(y)
\Mff= e# Map elements of L[X], generates φ(x ∗ y)
= e# Push 1 if the tables are equal, 0 otherwise
:| e# Push 1 if at least a mapping was isomorphic, 0 otherwise
{;}| e# If no mapping was isomorphic, pop the copy of Y off the stack
Bene, ora abbiamo un elenco di set come [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . L'idea qui è che, per proprietà transitiva, se due insiemi hanno almeno un elemento in comune, tutti i gruppi nei due insiemi sono isomorfi. Possono essere aggregati in un singolo set. Poiché L [X] non comparirà mai nelle combinazioni generate da L [X + ...] , dopo aver aggregato ogni serie di isomorfismi avrà un elemento unico. Quindi, per ottenere il numero di isomorfismi, è sufficiente contare quanti gruppi compaiono esattamente una volta in tutti gli insiemi di gruppi isomorfici. Per fare ciò, scartare i set in modo che sembrino [L [0], Y1, Y2, ..., L [1], Y1, ...] , ordinare l'elenco per creare cluster dello stesso gruppo e infine RLE-codificarlo.
:~ e# Unwrap sets of isomorphic groups
$ e# Sort list
e` e# RLE-encode list
{ }, e# Filter RLE elements:
0= e# Get number of occurrences
1= e# Keep element if occurrences == 1
, e# Push length of filtered list
e# This is the number of groups up to isomorphism
È tutto gente.