CJam, 189 187 octets
Celui-ci va être difficile à expliquer ... La complexité du temps est garantie 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=},,}{!!}?
Si vous êtes assez courageux, essayez-le en ligne . Sur mon portable merdique, je peux obtenir jusqu'à 6 avec l'interpréteur Java ou 5 dans l'interpréteur en ligne.
Explication
Je n'ai pas une grande formation en mathématiques (je viens de terminer mes études secondaires, je commence CS à l'université la semaine prochaine). Alors, soyez indulgent avec moi si je fais des erreurs, si j'énonce une évidence ou si je fais les choses de manière horriblement inefficace.
Mon approche est une force brute, bien que j'aie essayé de la rendre un peu plus intelligente. Les principales étapes sont les suivantes:
- Générer tous les opérandes possibles ∗ pour un groupe d'ordre n (c'est-à-dire énumérer tous les groupes d'ordre n );
- Générer toutes les bijections possibles φ entre deux groupes d'ordre n ;
- À l'aide des résultats des étapes 1 et 2, déterminez tous les isomorphismes entre deux groupes d'ordre n ;
- En utilisant le résultat de l'étape 3, comptez le nombre de groupes jusqu'à l'isomorphisme.
Avant d'examiner comment chaque étape est effectuée, retirons du code trivial:
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)
L'algorithme suivant ne fonctionne pas correctement avec n <4 , les cas de 0 à 3 sont traités avec une double négation.
Désormais, les éléments d'un groupe seront écrits comme {1, a, b, c, ...} , où 1 est l'élément d'identité. Dans l'implémentation CJam, les éléments correspondants sont {0, 1, 2, 3, ...} , où 0 est l'élément d'identité.
Commençons par l'étape 1. L'écriture de tous les opérateurs possibles pour un groupe d'ordre n équivaut à générer toutes les tables Cayley n × n valides . La première ligne et la première colonne sont triviales: elles sont toutes les deux {1, a, b, c, ...} (de gauche à droite, de haut en bas).
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
Savoir qu'une table Cayley est aussi un carré latin réduit (en raison de la propriété d'annulation) permet de générer les tables possibles ligne par ligne. À partir de la deuxième ligne (index 1), nous générons toutes les permutations uniques pour cette ligne, en gardant la première colonne fixée à la valeur de l'index.
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
Bien sûr, toutes ces permutations ne sont pas valides: chaque ligne et colonne doit contenir tous les éléments exactement une fois. Un bloc filtre est utilisé à cet effet (une valeur véridique conserve la permutation, une fausse la supprime):
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
Notez que je filtre à l'intérieur de la boucle de génération: cela rend le code un peu plus long (par rapport à la génération et au filtrage distincts), mais améliore considérablement les performances. Le nombre de permutations d'un ensemble de taille n est n! , la solution la plus courte nécessiterait donc beaucoup de mémoire et de temps.
Une liste de tables Cayley valides est une grande étape vers l'énumération des opérateurs, mais étant une structure 2D, elle ne peut pas vérifier l'associativité, qui est une propriété 3D. La prochaine étape consiste donc à filtrer les fonctions non associatives.
{ }, 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]
Phew! Beaucoup de travail, mais maintenant nous avons énuméré tous les groupes d'ordre n (ou mieux, les opérations dessus - mais l'ensemble est fixe, c'est donc la même chose). Étape suivante: trouver des isomorphismes. Un isomorphisme est une bijection entre deux de ces groupes tels que φ (x ∗ y) = φ (x) ∗ φ (y) . Générer ces bijections dans CJam est trivial: le Ne!
fera. Comment pouvons-nous les vérifier? Ma solution part de deux copies de la table Cayley pour x ∗ y . Sur une copie, φ est appliqué à tous les éléments, sans toucher à l'ordre des lignes ou des colonnes. Cela génère le tableau pour φ (x ∗ y) . Sur l'autre les éléments sont laissés comme ils sont, mais les lignes et les colonnes sont mises en correspondance par φ . Autrement dit, la ligne / colonnex devient la ligne / colonne φ (x) . Cela génère le tableau pour φ (x) ∗ φ (y) . Maintenant que nous avons les deux tableaux, il suffit de les comparer: s'ils sont les mêmes, nous avons trouvé un isomorphisme.
Bien sûr, nous devons également générer les paires de groupes pour tester l'isomorphisme. Nous avons besoin de toutes les 2 combinaisons des groupes. On dirait que CJam n'a pas d'opérateur pour les combinaisons. Nous pouvons les générer en prenant chaque groupe et en le combinant uniquement avec les éléments qui le suivent dans la liste. Fait amusant: le nombre de 2 combinaisons est n × (n - 1) / 2 , qui est également la somme des n - 1 premiers nombres naturels. Ces nombres sont appelés nombres triangulaires: essayez l'algorithme sur papier, une ligne par élément fixe, et vous comprendrez pourquoi.
: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
Le code ci-dessus corrige le premier élément de la paire, L [X] , et le combine avec d'autres groupes (appelons chacun de ces Y ). Il passe la paire à un bloc de test d'isomorphisme que je montrerai dans un instant. Le bloc rend une liste de valeurs de Y pour lesquels L [X] est isomorphe à Y . Ensuite, L [X] est ajouté à cette liste. Avant de comprendre pourquoi les listes sont ainsi configurées, regardons le test d'isomorphisme:
\_@ 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
Très bien, nous avons maintenant une liste d'ensembles comme [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . L'idée ici est que, par propriété transitive, si deux ensembles ont au moins un élément en commun, alors tous les groupes des deux ensembles sont isomorphes. Ils peuvent être agrégés en un seul ensemble. Comme L [X] n'apparaîtra jamais dans les combinaisons générées par L [X + ...] , après agrégation, chaque ensemble d'isomorphismes aura un élément unique. Ainsi, pour obtenir le nombre d'isomorphismes, il suffit de compter le nombre de groupes qui apparaissent exactement une fois dans tous les ensembles de groupes isomorphes. Pour ce faire, je déballe les ensembles pour qu'ils ressemblent à [L [0], Y1, Y2, ..., L [1], Y1, ...] , trie la liste pour créer des clusters du même groupe et enfin RLE-l'encode.
:~ 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
C'est tout, les amis.