Zählen von Gruppen einer bestimmten Größe

21

Gruppen

In der abstrakten Algebra ist eine Gruppe ein Tupel (G,) , wobei G eine Menge und eine Funktion G×GG so dass gilt:

  • Für alle in ist .G ( x y ) z = x ( y z )x,y,zG(xy)z=x(yz)

  • Es gibt ein Element in so dass für alle in , .G x G x e = xeGxGxe=x

  • Für jedes in existiert ein Element in so dass .G y G x y = exGyGxy=e

Die Ordnung einer Gruppe wird als die Anzahl von Elementen von definierten .G(G,)G

Für jede streng positive ganze Zahl existiert mindestens eine Gruppe der Ordnung . Zum Beispiel ist eine solche Gruppe, wobei und x + _ny = (x + y) \ modn .n ( C n , + n ) C n = { 0 , . . . , n - 1 } x + n y = ( x + y )nn(Cn,+n)Cn={0,...,n1}x+ny=(x+y)modn

Isomorphe Gruppen

Sei G:={1,2} und definiere durch xy=(x×y)mod3 . Dann ist 11=1=22 und 12=2=21 .

Ebenso ist 0+20=0=1+21 und 0+21=1=1+20 .

Obwohl die Elemente und Operationen der Gruppen (G,) und (C2,+2) unterschiedliche Namen haben, haben die Gruppen dieselbe Struktur.

Zwei Gruppen (G1,1) und (G2,2) gelten als isomorph, wenn eine Bijektion ϕ:G1G2 so dass ϕ(x1y)=ϕ(x)2ϕ(y) für alle x,y in G1 .

Nicht alle Gruppen derselben Ordnung sind isomorph. Zum Beispiel ist die Klein-Gruppe eine Gruppe der Ordnung 4, die nicht isomorph zu (C4,+4) .

Aufgabe

Schreiben Sie ein Programm oder eine Funktion, die eine nicht negative Ganzzahl n als Eingabe akzeptiert und die Anzahl der nicht isomorphen Gruppen der Ordnung n ausgibt oder zurückgibt .

Testfälle

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

(entnommen aus OEIS A000001 )

Zusätzliche Regeln

  • Die Ausführungszeit und die Speichernutzung sind nicht begrenzt.

  • Built-Ins, die diese Aufgabe banalisieren, wie Mathematics FiniteGroupCount, sind nicht zulässig.

  • Es gelten die Standardregeln für .

Dennis
quelle
14
Natürlich hat Mathematica eine eingebaute Lösung dafür. : /
Alex A.
1
Zitiert Peter (von einem Kommentar zu dem Sandbox Beitrag von Evolution of OEIS ): Formel „Wenn man sich der aussehen‚‘und‚Programm‘Abschnitte für zB A000001 , A000003, A000019 dann eine Antwort , die spezialisiert builtins nicht verwendet wird eine erfordern viel Forschung. " (Hervorhebung meiner.);)
Martin Ender
12
Einige sagen, dass es keine eingebauten Mathematica nicht hat, aber dies ist noch Gegenstand der Forschung. Andere Mythen besagen, dass Mathematica die eingebauten Elemente durch Lesen der Gedanken des Programmierers erstellt , aber auch dies wurde noch nicht bestätigt.
Fehler
1
@flawr das undokumentierte monkeys_on_typewritersBuildin deckt alles ab, was nicht in den dokumentierten Buildins enthalten ist.
Level River St
Warum ist (1 + 1)% 3 nicht 2?
Cabbie407,

Antworten:

16

CJam, 189 187 Bytes

Das wird schwer zu erklären sein ... Zeitkomplexität ist garantiert 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=},,}{!!}?

Wenn Sie mutig genug sind, versuchen Sie es online . Auf meinem beschissenen Laptop kann ich bis zu 6 mit dem Java-Interpreter oder 5 mit dem Online-Interpreter bekommen.

Erläuterung

Ich habe keinen großen mathematischen Hintergrund (bin gerade mit dem Abitur fertig, fange nächste Woche an der Uni an). Nehmen Sie es also mit, wenn ich Fehler mache, das Offensichtliche erkläre oder Dinge auf schrecklich ineffiziente Weise tue.

Mein Ansatz ist eine rohe Kraft, obwohl ich versucht habe, ihn etwas schlauer zu machen. Die Hauptschritte sind:

  1. Generiere alle möglichen Operanden für eine Gruppe der Ordnung n (dh zähle alle Gruppen der Ordnung n auf );
  2. Generiere alle möglichen Bijektionen φ zwischen zwei Gruppen der Ordnung n ;
  3. Bestimmen Sie anhand der Ergebnisse aus Schritt 1 und 2 alle Isomorphismen zwischen zwei Gruppen der Ordnung n ;
  4. Zählen Sie anhand des Ergebnisses aus Schritt 3 die Anzahl der Gruppen bis zur Isomorphie.

Bevor Sie sich ansehen, wie die einzelnen Schritte ausgeführt werden, sollten Sie zunächst einen einfachen Code aus dem Weg räumen:

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)

Der folgende Algorithmus funktioniert mit n <4 nicht richtig , die Fälle von 0 bis 3 werden mit einer doppelten Negation behandelt.

Von nun an werden die Elemente einer Gruppe als {1, a, b, c, ...} geschrieben , wobei 1 das Identitätselement ist. In der CJam-Implementierung sind die entsprechenden Elemente {0, 1, 2, 3, ...} , wobei 0 das Identitätselement ist.

Beginnen wir mit Schritt 1. Das Schreiben aller möglichen Operatoren für eine Gruppe der Ordnung n entspricht dem Generieren aller gültigen n × n Cayley-Tabellen . Die erste Zeile und Spalte sind trivial: Sie sind beide {1, a, b, c, ...} (von links nach rechts, von oben nach unten).

      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

Wenn Sie wissen, dass eine Cayley-Tabelle auch ein reduziertes lateinisches Quadrat ist (aufgrund der Löschungseigenschaft), können Sie die möglichen Tabellen zeilenweise generieren. Ausgehend von der zweiten Zeile (Index 1) generieren wir alle eindeutigen Permutationen für diese Zeile, wobei die erste Spalte auf den Wert des Index festgelegt bleibt.

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

Natürlich sind nicht alle diese Permutationen gültig: Jede Zeile und Spalte muss genau einmal alle Elemente enthalten. Zu diesem Zweck wird ein Filterblock verwendet (ein wahrer Wert behält die Permutation bei, ein falscher entfernt sie):

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

Beachten Sie, dass ich innerhalb der Generierungsschleife filtere: Dadurch wird der Code etwas länger (im Vergleich zu einer eindeutigen Generierung und Filterung), aber die Leistung wird erheblich verbessert. Die Anzahl der Permutationen einer Menge der Größe n ist n! Daher würde die kürzere Lösung viel Speicher und Zeit erfordern.

Eine Liste gültiger Cayley-Tabellen ist ein guter Schritt, um die Operatoren aufzulisten. Da es sich jedoch um eine 2D-Struktur handelt, kann die Assoziativität nicht überprüft werden, da es sich um eine 3D-Eigenschaft handelt. Der nächste Schritt ist das Herausfiltern von nicht assoziativen Funktionen.

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

Puh! Viel Arbeit, aber jetzt haben wir alle Gruppen der Ordnung n (oder besser die Operationen darauf - aber die Menge ist fest, so ist es das gleiche) aufgezählt . Nächster Schritt: Finden Sie Isomorphismen. Ein Isomorphismus ist eine Bijektion zwischen zwei dieser Gruppen, so dass φ (x ∗ y) = φ (x) ∗ φ (y) . Das Erzeugen dieser Bijektionen in CJam ist trivial: Ne!wird es tun. Wie können wir sie überprüfen? Meine Lösung geht von zwei Kopien der Cayley-Tabelle für x ∗ y aus . Bei einer Kopie wird φ auf alle Elemente angewendet, ohne die Reihenfolge der Zeilen oder Spalten zu berühren. Dies erzeugt die Tabelle für φ (x ∗ y) . Auf der anderen Seite bleiben die Elemente unverändert, aber Zeilen und Spalten werden durch φ abgebildet . Das heißt, die Zeile / Spaltex wird zur Zeile / Spalte φ (x) . Dies erzeugt die Tabelle für φ (x) ∗ φ (y) . Nachdem wir nun die beiden Tabellen haben, müssen wir sie nur vergleichen: Wenn sie gleich sind, haben wir einen Isomorphismus gefunden.

Natürlich müssen wir auch die Gruppenpaare erzeugen, an denen der Isomorphismus getestet werden soll. Wir brauchen alle 2-Kombinationen der Gruppen. Sieht so aus, als hätte CJam keinen Operator für Kombinationen. Wir können sie generieren, indem wir jede Gruppe nur mit den darauf folgenden Elementen in der Liste kombinieren. Unterhaltsame Tatsache: Die Anzahl der 2-Kombinationen ist n × (n - 1) / 2 , was auch die Summe der ersten n - 1 natürlichen Zahlen ist. Solche Zahlen werden Dreieckszahlen genannt: Probieren Sie den Algorithmus auf Papier aus, eine Zeile pro festes Element, und Sie werden sehen, warum.

: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

Der obige Code behebt das erste Element des Paares, L [X] , und kombiniert es mit anderen Gruppen (nennen wir jedes dieser Y ). Es übergibt das Paar an einen Isomorphismus-Testblock, den ich gleich zeigen werde. Der Block gibt eine Liste von Werten von zurück Y zurück, für die L [X] isomorph zu Y ist . Dann wird L [X] an diese Liste angehängt. Bevor wir verstehen, warum die Listen so aufgebaut sind, schauen wir uns den Isomorphismustest an:

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

Großartig, jetzt haben wir eine Liste von Mengen wie [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . Die Idee dabei ist, dass nach transitiver Eigenschaft, wenn zwei Mengen mindestens ein Element gemeinsam haben, alle Gruppen in den beiden Mengen isomorph sind. Sie können zu einem einzigen Satz zusammengefasst werden. Da L [X] in den durch L [X + ...] generierten Kombinationen niemals vorkommt, weist jede Menge von Isomorphismen nach der Aggregation ein eindeutiges Element auf. Um die Anzahl der Isomorphismen zu ermitteln, ist es ausreichend zu zählen, wie viele Gruppen in allen Gruppen isomorpher Gruppen genau einmal vorkommen. Dazu packe ich die Sets aus, sodass sie wie folgt aussehen: [L [0], Y1, Y2, ..., L [1], Y1, ...] , sortiere die Liste, um Cluster derselben Gruppe zu erstellen, und schließlich RLE-kodiere es.

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

Das war's Leute.

Andrea Biondo
quelle
2
Das ist eine verdammt gute Erklärung. Nett.
The_Basset_Hound
1
@The_Basset_Hound ... aaaand es ist jetzt fertig;)
Andrea Biondo
Ich betrachte meine eigene Antwort als nicht konkurrierend, daher habe ich diese akzeptiert.
Dennis
4

CJam, 73 Bytes

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

Die zeitliche Komplexität des obigen Codes ist schlimmer als O (n! N ) .

Eingabe n = 4 ist dem Online-Dolmetscher schon zu viel .

Verwendung des Java-Interpreters ist die Eingabe von n = 5 möglich, wenn Sie über genügend RAM und Geduld verfügen.

Gruppen finden

Bei einer gegebenen Gruppe (G, ∗) der Ordnung n können wir eine beliebige Bijektion φ auswählen : G -> C n, so dass φ (e) = 0 ist .

φ wird zu einem Isomorphismus von (G, ∗) und (C n , ∗ '), wenn wir ∗' durch x ∗ 'y = φ definieren (φ -1 (x) φ -1 (y)) .

Dies bedeutet, dass es ausreicht, alle Gruppenoperatoren in C n so zu untersuchen, dass 0 das neutrale Element ist.

Wir werden einen Gruppenoperator in C n durch ein rechteckiges Array T mit den Dimensionen n × n darstellen, so dass T [x] [y] = x ∗ y ist .

Um ein solches Array zu erzeugen, können wir zunächst eine Permutation von C n für jede seiner n Zeilen auswählen.

Auf diese Weise ist 0 in allen Zeilen (aber nicht unbedingt in allen Spalten ) vorhanden, was bedeutet, dass die dritte Bedingung (Existenz einer Inversen) erfüllt ist, unabhängig davon, was e sein mag.

Wir können e = 0 festlegen, indem wir voraussetzen , dass die erste Spalte von T gleich C n ist . Insbesondere gilt die zweite Bedingung (Vorhandensein eines neutralen Elements).

Um zu überprüfen, ob T einem Gruppenoperator entspricht, müssen Sie lediglich überprüfen, ob die erste Bedingung (Assoziativität) erfüllt ist. Dies kann erschöpfend erfolgen, indem überprüft wird, dass T [T [x] [y] [z] == T [x] [T [y] [z]] für alle x, y, z in C n ist .

Zählen von nicht isomorphen Gruppen

Das obige Verfahren zum Auffinden von Gruppen ergibt einige isomorphe Gruppen. Anstatt zu identifizieren, welche isomorph sind, erzeugen wir die Familie aller isomorphen Gruppen für jede von ihnen.

Dies kann erreicht werden, indem über alle Bijektionen φ: C n -> C n iteriert wird und das zugehörige Array Tφ bestimmt wird , das durch Tφ [x] [y] = φ –1 definiert ist (T [φ (x)] [φ (y )]) .

Alles, was zu tun bleibt, ist die Anzahl der verschiedenen Familien zu zählen.

Was der Code macht

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.
Dennis
quelle
Nett. Ich habe einen "dummen" Rohling ausprobiert, aber es war schwierig, an 5 heranzukommen, also habe ich Bytes gegen Geschwindigkeit eingetauscht.
Andrea Biondo
1

Python 2 , 515 507 Bytes

  • Acht Bytes gespart dank Dennis .
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)

Probieren Sie es online!


n Σnn

Link zur ausführlichen Version .

Jonathan Frech
quelle
Machen die Befehle von sund GMaterie? Wenn nicht, können Sie def f(k,*s):...f(~-k,j,*s)...und verwenden def c(k,*G):...c(~-k,s,*G).....
Dennis
@ Tennis tun sie nicht; Vielen Dank.
Jonathan Frech