Подсчет групп заданного размера

21

группы

В абстрактной алгебре группа - это кортеж , где - множество, а - функция такая, что имеет место следующее:G G × G G(грамм,*)грамм*грамм×граммграмм

  • Для всех в , .G ( x y ) z = x ( y z )Икс,Y,Zграмм(Икс*Y)*Zзнак равноИкс*(Y*Z)

  • Там существует элемент в , что для всех в , .G x G x e = xеграммИксграммИкс*езнак равноИкс

  • Для каждого в существует такой элемент в , что .G y G x y = eИксграммYграммИкс*Yзнак равное

Порядок группы определяется как число элементов .G(грамм,*)грамм

Для каждого строго положительного целого числа существует хотя бы одна группа порядка . Например, является такой группой, где и .п ( С п , + п ) С п = { 0 , . , , , n - 1 } x + n y = ( x + y )NN(СN,+N)СNзнак равно{0,,,,,N-1}x+ny=(x+y)modn

Изоморфные группы

Пусть и определим как . Тогда и .x y = ( x × y )G:={1,2}*1 1 = 1 = 2 2 1 2 = 2 = 2 1xy=(x×y)mod311=1=2212=2=21

Аналогично, и .0 + 2 1 = 1 = 1 + 2 00+20=0=1+210+21=1=1+20

Хотя элементы и операции групп и имеют разные имена, группы имеют одинаковую структуру.( C 2 , + 2 )(G,)(C2,+2)

Две группы и называются изоморфными, если существует биекция такая, что для всех в .( G 2 , 2 )(G1,1)(G2,2)ϕ:G1G2φ(Икс*1Y)знак равноφ(Икс)*2φ(Y)Икс,Yграмм1

Не все группы одного порядка изоморфны. Например, группа Клейна - это группа порядка 4, которая не изоморфна .(С4,+4)

задача

Напишите программу или функцию, которая принимает неотрицательное целое число n в качестве входных данных и печатает или возвращает количество неизоморфных групп порядка n .

Контрольные примеры

Input   Output
0       0
1       1
2       1
3       1
4       2
5       1
6       2
7       1
8       5
9       2
10      2
11      1
12      5
13      1
14      2
15      1
16      14
17      1
18      5
19      1
20      5

(взято из OEIS A000001 )

Дополнительные правила

  • Нет ограничений по времени выполнения или использованию памяти.

  • Встроенные модули, которые упрощают эту задачу, такие как Mathematica FiniteGroupCount, не допускаются.

  • Применяются стандартные правила .

Деннис
источник
14
Конечно, Mathematica имеет встроенную функцию для этого. : /
Алекс А.
1
Цитируя Питера (из комментария к посту «Песочница» Evolution of OEIS ): «Если вы посмотрите на разделы« формула »и« программа », например, для A000001 , A000003, A000019, то для ответа, в котором не используются специализированные встроенные функции, потребуется много исследований. " (Акцент мой.);)
Мартин Эндер
12
Некоторые говорят, что никаких встроенных в Mathematica нет , но это все еще предмет исследования. В других мифах говорится, что Mathematica создает встроенные функции, читая мысли программистов , но это тоже еще не подтверждено.
Flawr
1
@flawr недокументированная monkeys_on_typewritersвстроенная оболочка охватывает все, что не охвачено документированными встроенными функциями.
Уровень Река
Почему (1 + 1)% 3 не 2?
Cabbie407

Ответы:

16

CJam, 189 187 байт

Это будет сложно объяснить ... Сложность времени гарантированно будет 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=},,}{!!}?

Если вы достаточно смелы, попробуйте это онлайн . На моем дерьмовом ноутбуке я могу получить до 6 с переводчиком Java или 5 с онлайн-переводчиком.

объяснение

У меня нет большого образования по математике (только что закончил среднюю школу, на следующей неделе я начал учиться в университете). Поэтому терпите меня, если я совершаю ошибки, излагаю очевидное или совершаю ужасно неэффективные действия.

Мой подход - грубая сила, хотя я пытался сделать это немного умнее. Основные шаги:

  1. Генерация всех возможных операндов для группы порядка n (т. Е. Перечисление всех групп порядка n );
  2. Генерация всех возможных биекций φ между двумя группами порядка n ;
  3. Используя результаты шагов 1 и 2, определите все изоморфизмы между двумя группами порядка n ;
  4. Используя результат шага 3, посчитайте количество групп с точностью до изоморфизма.

Прежде чем посмотреть, как выполняется каждый шаг, давайте разберемся с некоторым тривиальным кодом:

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)

Следующий алгоритм не работает правильно при n <4 , случаи от 0 до 3 обрабатываются с двойным отрицанием.

Отныне элементы группы будут записываться как {1, a, b, c, ...} , где 1 - элемент идентичности. В реализации CJam соответствующими элементами являются {0, 1, 2, 3, ...} , где 0 - это элемент идентичности.

Начнем с шага 1. Запись всех возможных операторов для группы порядка n эквивалентна генерации всех действительных n × n таблиц Кэли . Первая строка и столбец тривиальны: они оба {1, a, b, c, ...} (слева направо, сверху вниз).

      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

Знание того, что таблица Кэли также является уменьшенным латинским квадратом (из-за свойства отмены), позволяет генерировать возможные таблицы построчно. Начиная со второй строки (индекс 1), мы генерируем все уникальные перестановки для этой строки, сохраняя в первом столбце значение индекса.

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

Конечно, не все эти перестановки верны: каждая строка и столбец должны содержать все элементы ровно один раз. Блок фильтра используется для этой цели (истинное значение сохраняет перестановку, ложное удаляет ее):

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

Обратите внимание, что я фильтрую внутри цикла генерации: это делает код немного длиннее (по сравнению с отдельной генерацией и фильтрацией), но значительно повышает производительность. Количество перестановок множества размера n равно n! поэтому более короткое решение потребует много памяти и времени.

Список допустимых таблиц Кейли - отличный шаг к перечислению операторов, но, будучи двумерной структурой, он не может проверить ассоциативность, которая является трехмерным свойством. Поэтому следующим шагом является фильтрация неассоциативных функций.

{                                 }, 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]

Уф! Много работы, но теперь мы перечислили все группы порядка n (или лучше, операции над ним - но набор фиксирован, так что это одно и то же). Следующий шаг: найти изоморфизмы. Изоморфизм - это биекция между двумя такими группами, что φ (x ∗ y) = φ (x) ∗ φ (y) . Генерация этих биекций в CJam тривиальна: Ne!сделаем это. Как мы можем их проверить? Мое решение начинается с двух копий таблицы Кэли для x ∗ y . На одном экземпляре φ применяется ко всем элементам, не затрагивая порядок строк или столбцов. Это порождает таблицу для φ (x ∗ y) . С другой стороны, элементы остаются без изменений, но строки и столбцы отображаются через φ . То есть строка / столбецх становится строкой / столбцом ф (х) . Это порождает таблицу для φ (x) ∗ φ (y) . Теперь, когда у нас есть две таблицы, мы просто должны сравнить их: если они одинаковы, мы нашли изоморфизм.

Конечно, нам также нужно сгенерировать пары групп для проверки изоморфизма. Нам нужны все 2-комбинации групп. Похоже, у CJam нет оператора для комбинаций. Мы можем генерировать их, беря каждую группу и комбинируя ее только с элементами, следующими за ней в списке. Интересный факт: количество 2-комбинаций равно n × (n - 1) / 2 , что также является суммой первых n - 1 натуральных чисел. Такие числа называются треугольными числами: попробуйте алгоритм на бумаге, по одной строке на каждый фиксированный элемент, и вы поймете, почему.

: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

Приведенный выше код фиксирует первый элемент пары, L [X] , и объединяет его с другими группами (назовем каждую из этих Y ). Он передает пару в тестовый блок изоморфизма, который я покажу позже. Блок возвращает список значений Y , для которых L [X] изоморфна Y . Затем L [X] добавляется в этот список. Прежде чем понять, почему списки настроены таким образом, давайте посмотрим на тест на изоморфизм:

\_@                                      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

Отлично, теперь у нас есть список множеств, таких как [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . Идея здесь состоит в том, что по транзитивному свойству, если любые два набора имеют хотя бы один общий элемент, то все группы в этих двух наборах изоморфны. Они могут быть объединены в один набор. Поскольку L [X] никогда не появится в комбинациях, сгенерированных L [X + ...] , после агрегирования каждый набор изоморфизмов будет иметь один уникальный элемент. Итак, чтобы получить количество изоморфизмов, достаточно подсчитать, сколько групп появляется ровно один раз во всех наборах изоморфных групп. Для этого я разворачиваю наборы так, чтобы они выглядели как [L [0], Y1, Y2, ..., L [1], Y1, ...] , сортирую список, чтобы создать кластеры из одной группы, и, наконец, RLE-кодировать это.

:~            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

Вот и все, ребята.

Андреа Биондо
источник
2
Это чертовски объяснение. Ницца.
The_Basset_Hound
1
@The_Basset_Hound ... ааа, и теперь он закончен;)
Андреа Биондо,
Я считаю свой ответ неконкурентным, поэтому я принял этот.
Деннис
4

CJam, 73 байта

0ri:Re!Rm*{:Tz0=R,=[R,Te_]m!{~ff{T==}e_}/=&},{:T,e!{:PPff{T==P#}}%$}%Q|,+

Временная сложность приведенного выше кода хуже, чем O (n! N ) .

Ввод n = 4 уже слишком для онлайн-переводчика .

Используя интерпретатор Java , можно ввести n = 5 , если у вас достаточно оперативной памяти и терпения.

Поиск групп

Для группы (G, ∗) порядка n можно выбрать произвольную биекцию φ: G -> C n такую, что φ (e) = 0 .

φ станет изоморфизмом (G, ∗) и ( Cn , ∗ '), если мы определим ∗' как x ∗ 'y = φ (φ -1 (x) ∗ φ -1 (y)) .

Это означает, что достаточно изучить все групповые операторы в C n , так что 0 является нейтральным элементом.

Мы представим групповой оператор в C n прямоугольным массивом T размерностей n × n , для которого T [x] [y] = x ∗ y .

Чтобы создать такой массив, мы можем начать с выбора перестановки C n для каждой из его n строк.

Таким образом, 0 будет присутствовать во всех строках (но не обязательно во всех столбцах ), означая, что третье условие (существование обратного) будет выполнено, независимо от того, что е может быть.

Мы можем исправить е = 0 , требуя , что первый столбец из Т равна С н . В частности, будет выполнено второе условие (наличие нейтрального элемента).

Чтобы проверить, что T соответствует групповому оператору, все, что осталось сделать, это проверить, что первое условие (ассоциативность) выполняется. Это можно сделать исчерпывающе, проверив, что T [T [x] [y]] [z] == T [x] [T [y] [z]] для всех x, y, z в C n .

Подсчет неизоморфных групп

Приведенный выше метод поиска групп даст некоторые изоморфные группы. Вместо определения того, какие изоморфны, мы генерируем семейство всех изоморфных групп для каждой из них.

Это может быть достигнуто путем итерации по всем биекциям φ: C n -> C n и определения связанного массива , определенного как Tφ [x] [y] = φ -1 (T [φ (x)] [φ (y) )]) .

Осталось только подсчитать количество разных семей.

Что делает код

0         e# Push 0. For input 0, the remaining code will crash, leaving
          e# this 0 on the stack.
ri:R      e# Read an integer from STDIN and save it in R.
e!        e# Push all permutations of [0 ... R-1].
Rm*       e# Push all arrays of 6 permutations of [0 ... R-1].
{         e# Filter; for each array:
  :T      e#   Save it in T.
  z0=R,=  e#   Check if the first column equals [0 ... R-1].
  [R,Te_] e#   Push [0 ... R-1] and a flattened T.
  m!{     e#   For both pairs (any order):
    ~     e#     Unwrap the pair.
    ff{   e#     For each X in the first: For each Y in the second:
      T== e#       Push T[X][Y].
    }     e#
  }/      e#
  =       e#   Check for equality, i.e., associativity.
  &       e#   Bitwise AND with the previous Boolean
},        e# Keep T iff the result was truthy.
{         e# For each kept array:
  :T      e#   Save it in T
  ,e!     e#   Push all permutations of [0 ... R-1].
  {       e#   For each permutation:
    :PP   e#     Save it in P. Push a copy.
    ff{   e#     For each X in P: For each Y in P:
      T== e#       Push T[X][Y].
      P#  e#       Find its index in P.
    }     e#
  }%      e#
  $       e#   Sort the results.
}%        e#
Q|,       e# Deduplicate and count.
+         e# Add the result to the 0 on the stack.
Деннис
источник
Ницца. Я попробовал «тупого» зверя, но было трудно получить 5, поэтому я обменял байты на скорость.
Андреа Биондо
1

Python 2 , 515 507 байт

  • Сохранено восемь байтов благодаря Деннису .
def F(n):
 def f(k,*s):n==len(set(s))and S.add(s);{k and f(~-k,j,*s)for j in I}
 def c(k,*G):k and{s in G or c(~-k,s,*G)for s in S}or(I in G)&all((o(x,y)in G)&any(I==o(z,x)for z in G)for x in G for y in G)and A.add(G)
 S=set();A=S-S;I=tuple(range(n));o=lambda x,y:tuple(y[x[j]]for j in I);i=lambda G,H:any(all(o(H[B[i]],H[B[j]])==H[B[[k for k in I if G[k]==o(G[i],G[j])][0]]]for i in I for j in I)for B in S);f(n);c(n);K=list(A);[G in K and{G!=H and i(G,H)and K.remove(H)for H in K}for G in A];return len(K)

Попробуйте онлайн!


N ΣNN

Ссылка на подробную версию .

Джонатан Фрех
источник
Имеют ли порядок sи Gзначение? Если нет, вы можете использовать def f(k,*s):...f(~-k,j,*s)...и def c(k,*G):...c(~-k,s,*G).....
Деннис
@ Денис Они не делают; Спасибо.
Джонатан Фрех