CJam, 189 187 bytes
Esto va a ser difícil de explicar ... La complejidad del tiempo está garantizada 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 eres lo suficientemente valiente, pruébalo en línea . En mi horrible computadora portátil, puedo obtener hasta 6 con el intérprete de Java o 5 en el intérprete en línea.
Explicación
No tengo una gran experiencia en matemáticas (acabo de terminar la escuela secundaria, comenzando CS en la universidad la próxima semana). Así que tengan paciencia conmigo si cometo errores, digo lo obvio o hago las cosas de maneras terriblemente ineficaces.
Mi enfoque es una fuerza bruta, aunque traté de hacerlo un poco más inteligente. Los pasos principales son:
- Genere todos los operandos posibles ∗ para un grupo de orden n (es decir, enumere todos los grupos de orden n );
- Genere todas las biyecciones posibles φ entre dos grupos de orden n ;
- Usando los resultados de los pasos 1 y 2, determine todos los isomorfismos entre dos grupos de orden n ;
- Usando el resultado del paso 3, cuente el número de grupos hasta el isomorfismo.
Antes de ver cómo se realiza cada paso, eliminemos un código 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)
El siguiente algoritmo no funciona correctamente con n <4 , los casos de 0 a 3 se manejan con una doble negación.
De ahora en adelante, los elementos de un grupo se escribirán como {1, a, b, c, ...} , donde 1 es el elemento de identidad. En la implementación de CJam, los elementos correspondientes son {0, 1, 2, 3, ...} , donde 0 es el elemento de identidad.
Comencemos por el paso 1. Escribir todos los operadores posibles para un grupo de orden n es equivalente a generar todas las tablas n × n Cayley válidas . La primera fila y la columna son triviales: ambas son {1, a, b, c, ...} (de izquierda a derecha, de arriba a abajo).
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
Saber que una tabla de Cayley también es un cuadrado latino reducido (debido a la propiedad de cancelación) permite generar las posibles tablas fila por fila. A partir de la segunda fila (índice 1), generamos todas las permutaciones únicas para esa fila, manteniendo la primera columna fija en el valor del índice.
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
No todas esas permutaciones son válidas, por supuesto: cada fila y columna debe contener todos los elementos exactamente una vez. Se utiliza un bloque de filtro para este propósito (un valor verdadero mantiene la permutación, un valor falso lo elimina):
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
Tenga en cuenta que estoy filtrando dentro del ciclo de generación: esto hace que el código sea un poco más largo (en comparación con la generación y el filtrado distintos), pero mejora enormemente el rendimiento. El número de permutaciones de un conjunto de tamaño. n es n! , por lo que la solución más corta requeriría mucha memoria y tiempo.
Una lista de tablas de Cayley válidas es un gran paso para enumerar los operadores, pero al ser una estructura 2D, no puede verificar la asociatividad, que es una propiedad 3D. Entonces, el siguiente paso es filtrar las funciones no asociativas.
{ }, 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]
¡Uf! Mucho trabajo, pero ahora hemos enumerado todos los grupos de orden n (o mejor, las operaciones en él, pero el conjunto es fijo, por lo que es lo mismo). Siguiente paso: encontrar isomorfismos. Un isomorfismo es una biyección entre dos de esos grupos de modo que φ (x ∗ y) = φ (x) ∗ φ (y) . Generar esas biyecciones en CJam es trivial: Ne!
lo hará. ¿Cómo podemos verificarlos? Mi solución comienza con dos copias de la tabla de Cayley para x ∗ y . En una copia, φ se aplica a todos los elementos, sin tocar el orden de las filas o columnas. Esto genera la tabla para φ (x ∗ y) . En el otro, los elementos se dejan como están, pero las filas y columnas se asignan a través de φ . Es decir, la fila / columnax se convierte en la fila / columna φ (x) . Esto genera la tabla para φ (x) ∗ φ (y) . Ahora que tenemos las dos tablas, solo tenemos que compararlas: si son iguales, hemos encontrado un isomorfismo.
Por supuesto, también necesitamos generar los pares de grupos para probar el isomorfismo. Necesitamos todas las 2 combinaciones de los grupos. Parece que CJam no tiene operador para las combinaciones. Podemos generarlos tomando cada grupo y combinándolo solo con los elementos que lo siguen en la lista. Dato curioso: el número de 2 combinaciones es n × (n - 1) / 2 , que también es la suma de los primeros n - 1 números naturales. Dichos números se llaman números triangulares: pruebe el algoritmo en papel, una fila por elemento fijo, y verá por qué.
: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
El código anterior corrige el primer elemento del par, L [X] , y lo combina con otros grupos (llamemos a cada uno de esos Y ). Pasa el par a un bloque de prueba de isomorfismo que mostraré en un momento. El bloque devuelve una lista de valores de Y para los que L [X] es isomorfo a Y . Entonces L [X] se agrega a esta lista. Antes de entender por qué las listas están configuradas de tal manera, veamos la prueba de 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
Genial, ahora tenemos una lista de conjuntos como [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . La idea aquí es que, por propiedad transitiva, si dos conjuntos tienen al menos un elemento en común, entonces todos los grupos en los dos conjuntos son isomórficos. Se pueden agregar en un solo conjunto. Como L [X] nunca aparecerá en las combinaciones generadas por L [X + ...] , después de agregar cada conjunto de isomorfismos tendrá un elemento único. Entonces, para obtener el número de isomorfismos, es suficiente contar cuántos grupos aparecen exactamente una vez en todos los grupos de grupos isomórficos. Para hacer esto, desenvuelvo los conjuntos para que se vean como [L [0], Y1, Y2, ..., L [1], Y1, ...] , clasifico la lista para crear grupos del mismo grupo y finalmente 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
Eso es todo amigos.