Längste nicht wiederholende Game-of-Life-Sequenz

16

Bestimmen Sie bei einer positiven ganzen Zahl N das Startmuster auf einem N x N-Gitter, das die längste nicht wiederholte Sequenz nach den Regeln des Spiels des Lebens ergibt, und enden Sie mit einem festen Muster (Zyklus der Länge 1), das auf einem Torus gespielt wird.

Das Ziel ist nicht das kürzeste Programm, sondern das schnellste.

Da die Welt endlich ist, geraten Sie irgendwann in eine Schleife und wiederholen so einen bereits besuchten Zustand. Wenn diese Schleife die Periode 1 hat, ist das Startmuster ein gültiger Kandidat.

Ausgabe: Startmuster und Gesamtzahl der eindeutigen Zustände in der Sequenz (einschließlich Startmuster).

Jetzt ist der 1x1-Torus etwas Besonderes, da eine Zelle als benachbart zu sich selbst betrachtet werden kann oder nicht, aber in der Praxis gibt es kein Problem, eine einzelne lebende Zelle stirbt in jedem Fall einfach (an Überfüllung oder Einsamkeit). Somit ergibt Eingang 1 eine Sequenz der Länge 2, wobei die Sequenz eine lebende Zelle ist und dann für immer tot ist.

Die Motivation für diese Frage ist, dass es sich um ein Analogon zur Funktion der beschäftigten Biber handelt, aber auf jeden Fall weniger komplex, da wir an das Gedächtnis gebunden sind. Dies wird eine schöne Sequenz sein, die auch in OEIS aufgenommen werden kann.

Für N = 3 ist die Sequenzlänge 3, jedes Muster auf der linken Seite erreicht ein vollständig schwarzes 3 × 3-Quadrat und stirbt dann. (Alle Muster, die Teil eines Zyklus sind, wurden entfernt.)

Diagramm der Zustände

Per Alexandersson
quelle
1
Ah, alles klar. Der beste Code ist derjenige, der es schafft, die Sequenzlänge für das größte N innerhalb von beispielsweise 2 Stunden zu berechnen. Die offensichtliche Komplexität ist 2 ^ (N ^ 2). Wenn es also möglich ist, dies zu verbessern, wäre dies schön.
Per Alexandersson
1
Bei nicht-trivialen Größen ist jedes Muster Teil einer Isomorphismusklasse von 8N ^ 2-Mustern. Wenn es also eine schnelle Möglichkeit zur Kanonisierung gibt, gibt dies einen moderaten Schub.
Peter Taylor
1
Wurde diese Sequenz zu OEIS hinzugefügt?
mbomb007
1
Nicht dass mir das bewusst wäre, würde mich freuen, es dort zu sehen.
Per Alexandersson
1
Ich habe diese Sequenz (2, 2, 3, 10, 52, 91) als A294241 beim OEIS eingereicht .
Peter Kagey

Antworten:

13

C ++ bis N = 6

3x3 answer 3: 111 000 000                                                                                        
4x4 answer 10: 1110 0010 1100 0000                                                                               
5x5 answer 52: 11010 10000 11011 10100 00000                                                                     
6x6 answer 91: 100011 010100 110011 110100 101000 100000                                                         

Diese Technik konzentriert sich auf eine schnelle Next-State-Funktion. Jede Karte wird als Bitmaske mit N ^ 2 Bits dargestellt, und Bit-Twiddly-Tricks werden verwendet, um den nächsten Zustand für alle Zellen gleichzeitig zu berechnen. nextkompiliert bis zu 200 100 Montageanweisungen für N <= 8. Dann führen wir einfach die Standard-Alles-versuchen-Zustände-bis-sie-wiederholen aus und wählen den längsten aus.

Dauert einige Sekunden bis zu 5x5, einige Stunden für 6x6.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

#define N 6

typedef uint64_t board;

// board is N bits by N bits, with indexes like this (N=4):                                                        
//  0  1  2  3                                                                                                     
//  4  5  6  7                                                                                                     
//  8  9 10 11                                                                                                     
// 12 13 14 15                                                                                                     

#if N==3
#define LEFT_COL (1 + (1<<3) + (1<<6))
#define RIGHT_COL ((1<<2) + (1<<5) + (1<<8))
#define ALL 0x1ffULL
#elif N==4
#define LEFT_COL 0x1111ULL
#define RIGHT_COL 0x8888ULL
#define ALL 0xffffULL
#elif N==5
#define LEFT_COL (1ULL + (1<<5) + (1<<10) + (1<<15) + (1<<20))
#define RIGHT_COL ((1ULL<<4) + (1<<9) + (1<<14) + (1<<19) + (1<<24))
#define ALL 0x1ffffffULL
#elif N==6
#define LEFT_COL (1 + (1<<6) + (1<<12) + (1<<18) + (1<<24) + (1ULL<<30))
#define RIGHT_COL ((1<<5) + (1<<11) + (1<<17) + (1<<23) + (1<<29) + (1ULL<<35))
#define ALL 0xfffffffffULL
#else
#error "bad N"
#endif

static inline board north(board b) {
  return (b >> N) + (b << N*N-N);
}
static inline board south(board b) {
  return (b << N) + (b >> N*N-N);
}
static inline board west(board b) {
  return ((b & ~LEFT_COL) >> 1) + ((b & LEFT_COL) << N-1);
}
static inline board east(board b) {
  return ((b & ~RIGHT_COL) << 1) + ((b & RIGHT_COL) >> N-1);
}

board next(board b) {
  board n1 = north(b);
  board n2 = south(b);
  board n3 = west(b);
  board n4 = east(b);
  board n5 = north(n3);
  board n6 = north(n4);
  board n7 = south(n3);
  board n8 = south(n4);

  // add all the bits bitparallel-y to a 2-bit accumulator with overflow
  board a0 = 0;
  board a1 = 0;
  board overflow = 0;
#define ADD(x) overflow |= a0 & a1 & x; a1 ^= a0 & x; a0 ^= x;

  a0 = n1; // no carry yet
  a1 ^= a0 & n2; a0 ^= n2; // no overflow yet
  a1 ^= a0 & n3; a0 ^= n3; // no overflow yet
  ADD(n4);
  ADD(n5);
  ADD(n6);
  ADD(n7);
  ADD(n8);
  return (a1 & (b | a0)) & ~overflow & ALL;
}
void print(board b) {
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
      printf("%d", (int)(b >> i*N+j & 1));
    }
    printf(" ");
  }
  if (b >> N*N) printf("*");
  printf("\n");
}

int main(int argc, char *argv[]) {
  // Somewhere in the starting pattern there are a 1 and 0 together.  Using translational                          
  // and rotational symmetry, we can put these in the first two bits.  So we need only consider                    
  // 1 mod 4 boards.                                                                                               

  board steps[10000];
  int maxsteps = -1;
  for (board b = 1; b < 1ULL << N*N; b += 4) {
    int nsteps = 0;
    board x = b;
    while (true) {
      int repeat = steps + nsteps - find(steps, steps + nsteps, x);
      if (repeat > 0) {
        if (repeat == 1 && nsteps > maxsteps) {
          printf("%d: ", nsteps);
          print(b);
          maxsteps = nsteps;
        }
        break;
      }
      steps[nsteps++] = x;
      x = next(x);
    }
  }
}
Keith Randall
quelle
1
Sie können eine moderate Verbesserung erzielen, nextwenn Sie zählen anstatt zu sortieren. #define H(x,y){x^=y;y&=(x^y);}und dann so etwas wieH(n1,n2);H(n3,n4);H(n5,n6);H(n7,n8);H(n1,n3);H(n5,n7);H(n2,n4);H(n6,n8);H(n1,n5);H(n3,n7);H(n2,n6);H(n2,n3);H(n2,n5); return n2 & (b | n1) & ~(n3|n4|n5|n6|n7|n8) & ALL;
Peter Taylor
Wirklich coole Lösung!
Per Alexandersson
@PeterTaylor: danke, ich habe ein anderes Schema für das Zählen implementiert und eine Reihe von Anweisungen gespeichert.
Keith Randall
9

Ich sehe folgende Lösungsansätze:

  • Schwere Theorie. Ich weiß, dass es Literatur über das Leben auf einem Torus gibt, aber ich habe nicht viel davon gelesen.
  • Brute Force Forwards: Überprüfen Sie für jedes mögliche Board dessen Punktzahl. Dies ist im Grunde das, was Matthew und Keith tun, obwohl Keith die Anzahl der zu überprüfenden Bretter um den Faktor 4 reduziert.
    • Optimierung: kanonische Darstellung. Wenn wir überprüfen können, ob eine Tafel in kanonischer Darstellung ist, erhalten wir eine Beschleunigung um den Faktor 8N ^ 2. (Es gibt auch Teilansätze mit kleineren Äquivalenzklassen).
    • Optimierung: DP. Zwischenspeichern Sie die Punktzahl für jedes Brett, sodass Sie nicht durch das Brett laufen müssen, bis es konvergiert oder auseinander läuft, sondern bis Sie ein Brett finden, das Sie zuvor gesehen haben. Im Prinzip würde dies eine Beschleunigung um einen Faktor der durchschnittlichen Punktzahl / Zykluslänge (vielleicht 20 oder mehr) ergeben, aber in der Praxis werden wir wahrscheinlich stark tauschen. ZB für N = 6 benötigen wir Kapazität für 2 ^ 36 Scores, was bei einem Byte pro Score 16 GB entspricht, und wir benötigen Direktzugriff, sodass wir keine gute Cache-Lokalität erwarten können.
    • Kombiniere die beiden. Für N = 6 würde die vollständige kanonische Darstellung es uns ermöglichen, den DP-Cache auf etwa 60 Mega-Scores zu reduzieren. Dies ist ein vielversprechender Ansatz.
  • Brute Force rückwärts. Dies erscheint zunächst seltsam, aber wenn wir davon ausgehen, dass wir Stillleben leicht finden und die Next(board)Funktion leicht umkehren können, sehen wir, dass dies einige Vorteile hat, wenn auch nicht so viele, wie ich ursprünglich gehofft hatte.
    • Wir kümmern uns überhaupt nicht um divergierende Boards. Keine große Ersparnis, weil sie ziemlich selten sind.
    • Wir müssen nicht für alle Boards Scores speichern, daher sollte der Speicherdruck geringer sein als beim Forward-DP-Ansatz.
    • Rückwärts zu arbeiten ist eigentlich recht einfach, wenn ich eine Technik, die ich in der Literatur gesehen habe, im Kontext der Aufzählung von Stillleben verändere. Es funktioniert, indem jede Spalte als Buchstabe in einem Alphabet behandelt wird und dann beobachtet wird, dass eine Folge von drei Buchstaben den mittleren Buchstaben in der nächsten Generation bestimmt. Die Parallele zur Aufzählung von Stillleben ist so eng, dass ich sie zu einer nur geringfügig umständlichen Methode zusammengefügt habe Prev2.
    • Es könnte den Anschein haben, als könnten wir einfach die Stillleben kanonisieren und etwas wie die 8N ^ 2-Beschleunigung zu sehr geringen Kosten erhalten. Empirisch gesehen lässt sich die Anzahl der berücksichtigten Boards jedoch immer noch stark reduzieren, wenn wir bei jedem Schritt eine Kanonisierung vornehmen.
    • Ein überraschend hoher Anteil der Boards hat eine Punktzahl von 2 oder 3, so dass immer noch Speicherdruck besteht. Ich fand es notwendig, spontan zu kanonisieren, anstatt die vorherige Generation aufzubauen und dann zu kanonisieren. Es könnte interessant sein, die Speichernutzung durch eine Tiefensuche anstelle einer Breitensuche zu reduzieren, aber ohne den Stapel zu überlaufen und ohne redundante Berechnungen zu machen, ist ein Co-Routine- / Fortsetzungsansatz für die Aufzählung der vorherigen Karten erforderlich.

Ich glaube nicht, dass ich durch die Mikrooptimierung mit dem Code von Keith Schritt halten kann, aber aus Gründen des Interesses werde ich posten, was ich habe. Dies löst N = 5 in ungefähr einer Minute auf einer 2-GHz-Maschine unter Verwendung von Mono 2.4 oder .Net (ohne PLINQ) und in ungefähr 20 Sekunden unter Verwendung von PLINQ; N = 6 läuft viele Stunden.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sandbox {
    class Codegolf9393 {
        internal static void Main() {
            new Codegolf9393(4).Solve();
        }

        private readonly int _Size;
        private readonly uint _AlphabetSize;
        private readonly uint[] _Transitions;
        private readonly uint[][] _PrevData1;
        private readonly uint[][] _PrevData2;
        private readonly uint[,,] _CanonicalData;

        private Codegolf9393(int size) {
            if (size > 8) throw new NotImplementedException("We need to fit the bits in a ulong");

            _Size = size;
            _AlphabetSize = 1u << _Size;

            _Transitions = new uint[_AlphabetSize * _AlphabetSize * _AlphabetSize];
            _PrevData1 = new uint[_AlphabetSize * _AlphabetSize][];
            _PrevData2 = new uint[_AlphabetSize * _AlphabetSize * _AlphabetSize][];
            _CanonicalData = new uint[_Size, 2, _AlphabetSize];

            InitTransitions();
        }

        private void InitTransitions() {
            HashSet<uint>[] tmpPrev1 = new HashSet<uint>[_AlphabetSize * _AlphabetSize];
            HashSet<uint>[] tmpPrev2 = new HashSet<uint>[_AlphabetSize * _AlphabetSize * _AlphabetSize];
            for (int i = 0; i < tmpPrev1.Length; i++) tmpPrev1[i] = new HashSet<uint>();
            for (int i = 0; i < tmpPrev2.Length; i++) tmpPrev2[i] = new HashSet<uint>();

            for (uint i = 0; i < _AlphabetSize; i++) {
                for (uint j = 0; j < _AlphabetSize; j++) {
                    uint prefix = Pack(i, j);
                    for (uint k = 0; k < _AlphabetSize; k++) {
                        // Build table for forwards checking
                        uint jprime = 0;
                        for (int l = 0; l < _Size; l++) {
                            uint count = GetBit(i, l-1) + GetBit(i, l) + GetBit(i, l+1) + GetBit(j, l-1) + GetBit(j, l+1) + GetBit(k, l-1) + GetBit(k, l) + GetBit(k, l+1);
                            uint alive = GetBit(j, l);
                            jprime = SetBit(jprime, l, (count == 3 || (alive + count == 3)) ? 1u : 0u);
                        }
                        _Transitions[Pack(prefix, k)] = jprime;

                        // Build tables for backwards possibilities
                        tmpPrev1[Pack(jprime, j)].Add(k);
                        tmpPrev2[Pack(jprime, i, j)].Add(k);
                    }
                }
            }

            for (int i = 0; i < tmpPrev1.Length; i++) _PrevData1[i] = tmpPrev1[i].ToArray();
            for (int i = 0; i < tmpPrev2.Length; i++) _PrevData2[i] = tmpPrev2[i].ToArray();

            for (uint col = 0; col < _AlphabetSize; col++) {
                _CanonicalData[0, 0, col] = col;
                _CanonicalData[0, 1, col] = VFlip(col);
                for (int rot = 1; rot < _Size; rot++) {
                    _CanonicalData[rot, 0, col] = VRotate(_CanonicalData[rot - 1, 0, col]);
                    _CanonicalData[rot, 1, col] = VRotate(_CanonicalData[rot - 1, 1, col]);
                }
            }
        }

        private ICollection<ulong> Prev2(bool stillLife, ulong next, ulong prev, int idx, ICollection<ulong> accum) {
            if (stillLife) next = prev;

            if (idx == 0) {
                for (uint a = 0; a < _AlphabetSize; a++) Prev2(stillLife, next, SetColumn(0, idx, a), idx + 1, accum);
            }
            else if (idx < _Size) {
                uint i = GetColumn(prev, idx - 2), j = GetColumn(prev, idx - 1);
                uint jprime = GetColumn(next, idx - 1);
                uint[] succ = idx == 1 ? _PrevData1[Pack(jprime, j)] : _PrevData2[Pack(jprime, i, j)];
                foreach (uint b in succ) Prev2(stillLife, next, SetColumn(prev, idx, b), idx + 1, accum);
            }
            else {
                // Final checks: does the loop round work?
                uint a0 = GetColumn(prev, 0), a1 = GetColumn(prev, 1);
                uint am = GetColumn(prev, _Size - 2), an = GetColumn(prev, _Size - 1);
                if (_Transitions[Pack(am, an, a0)] == GetColumn(next, _Size - 1) &&
                    _Transitions[Pack(an, a0, a1)] == GetColumn(next, 0)) {
                    accum.Add(Canonicalise(prev));
                }
            }

            return accum;
        }

        internal void Solve() {
            DateTime start = DateTime.UtcNow;
            ICollection<ulong> gen = Prev2(true, 0, 0, 0, new HashSet<ulong>());
            for (int depth = 1; gen.Count > 0; depth++) {
                Console.WriteLine("Length {0}: {1}", depth, gen.Count);
                ICollection<ulong> nextGen;

                #if NET_40
                nextGen = new HashSet<ulong>(gen.AsParallel().SelectMany(board => Prev2(false, board, 0, 0, new HashSet<ulong>())));
                #else
                nextGen = new HashSet<ulong>();
                foreach (ulong board in gen) Prev2(false, board, 0, 0, nextGen);
                #endif

                // We don't want the still lifes to persist or we'll loop for ever
                if (depth == 1) {
                    foreach (ulong stilllife in gen) nextGen.Remove(stilllife);
                }

                gen = nextGen;
            }
            Console.WriteLine("Time taken: {0}", DateTime.UtcNow - start);
        }

        private ulong Canonicalise(ulong board)
        {
            // Find the minimum board under rotation and reflection using something akin to radix sort.
            Isomorphism canonical = new Isomorphism(0, 1, 0, 1);
            for (int xoff = 0; xoff < _Size; xoff++) {
                for (int yoff = 0; yoff < _Size; yoff++) {
                    for (int xdir = -1; xdir <= 1; xdir += 2) {
                        for (int ydir = 0; ydir <= 1; ydir++) {
                            Isomorphism candidate = new Isomorphism(xoff, xdir, yoff, ydir);

                            for (int col = 0; col < _Size; col++) {
                                uint a = canonical.Column(this, board, col);
                                uint b = candidate.Column(this, board, col);

                                if (b < a) canonical = candidate;
                                if (a != b) break;
                            }
                        }
                    }
                }
            }

            ulong canonicalValue = 0;
            for (int i = 0; i < _Size; i++) canonicalValue = SetColumn(canonicalValue, i, canonical.Column(this, board, i));
            return canonicalValue;
        }

        struct Isomorphism {
            int xoff, xdir, yoff, ydir;

            internal Isomorphism(int xoff, int xdir, int yoff, int ydir) {
                this.xoff = xoff;
                this.xdir = xdir;
                this.yoff = yoff;
                this.ydir = ydir;
            }

            internal uint Column(Codegolf9393 _this, ulong board, int col) {
                uint basic = _this.GetColumn(board, xoff + col * xdir);
                return _this._CanonicalData[yoff, ydir, basic];
            }
        }

        private uint VRotate(uint col) {
            return ((col << 1) | (col >> (_Size - 1))) & (_AlphabetSize - 1);
        }

        private uint VFlip(uint col) {
            uint replacement = 0;
            for (int row = 0; row < _Size; row++)
                replacement = SetBit(replacement, row, GetBit(col, _Size - row - 1));
            return replacement;
        }

        private uint GetBit(uint n, int bit) {
            bit %= _Size;
            if (bit < 0) bit += _Size;

            return (n >> bit) & 1;
        }

        private uint SetBit(uint n, int bit, uint value) {
            bit %= _Size;
            if (bit < 0) bit += _Size;

            uint mask = 1u << bit;
            return (n & ~mask) | (value == 0 ? 0 : mask);
        }

        private uint Pack(uint a, uint b) { return (a << _Size) | b; }
        private uint Pack(uint a, uint b, uint c) {
            return (((a << _Size) | b) << _Size) | c;
        }

        private uint GetColumn(ulong n, int col) {
            col %= _Size;
            if (col < 0) col += _Size;
            return (_AlphabetSize - 1) & (uint)(n >> (col * _Size));
        }

        private ulong SetColumn(ulong n, int col, uint value) {
            col %= _Size;
            if (col < 0) col += _Size;

            ulong mask = (_AlphabetSize - 1) << (col * _Size);
            return (n & ~mask) | (((ulong)value) << (col * _Size));
        }
    }
}
Peter Taylor
quelle
Ich arbeite auch an einer anderen Version, um von festen Punkten rückwärts zu laufen. Ich habe die Fixpunkte bereits bis zu N = 8 aufgezählt (für N = 8 gibt es 84396613 davon vor der Kanonisierung). Ich habe eine einfache vorschau, aber es ist zu langsam. Ein Teil des Problems ist nur die Größe der Dinge, für N = 6 hat die leere Tafel 574384901 Vorgänger (vor der Kanonisierung).
Keith Randall
1
3 Tage und 11 Stunden, um zu bestätigen, dass 91 die Antwort für 6x6 ist.
Peter Taylor
1

Faktor

USING: arrays grouping kernel locals math math.functions math.parser math.order math.ranges math.vectors sequences sequences.extras ;
IN: longest-gof-pattern

:: neighbors ( x y game -- neighbors )
game length :> len 
x y game -rot 2array {
    { -1 -1 }
    { -1 0 }
    { -1 1 }
    { 0 -1 }
    { 0 1 }
    { 1 -1 }
    { 1 0 }
    { 1 1 }
} [
    v+ [
        dup 0 <
        [ dup abs len mod - abs len mod ] [ abs len mod ]
        if
    ] map
] with map [ swap [ first2 ] dip nth nth ] with map ;

: next ( game -- next )
dup [
    [
        neighbors sum
        [ [ 1 = ] [ 2 3 between? ] bi* and ]
        [ [ 0 = ] [ 3 = ] bi* and ] 2bi or 1 0 ?
    ] curry curry map-index
] curry map-index ;

: suffixes ( seq -- suffixes )
{ }
[ [ [ suffix ] curry map ] [ 1array 1array ] bi append ]
reduce ;

! find largest repeating pattern
: LRP ( seq -- pattern )
dup length iota
[ 1 + [ reverse ] dip group [ reverse ] map reverse ] with
map dup [ dup last [ = ] curry map ] map
[ suffixes [ t [ and ] reduce ] map [ ] count ] map
dup supremum [ = ] curry find drop swap nth last ;

: game-sequence ( game -- seq )
1array [
    dup [
        dup length 2 >
        [ 2 tail-slice* [ first ] [ last ] bi = not ]
        [ drop t ] if
    ] [ LRP length 1 > not ] bi and
] [ dup last next suffix ] while ;

: pad-to-with ( str len padstr -- rstr )
[ swap dup length swapd - ] dip [ ] curry replicate ""
[ append ] reduce prepend ;

:: all-NxN-games ( n -- games )
2 n sq ^ iota [
    >bin n sq "0" pad-to-with n group
    [ [ 48 = 0 1 ? ] { } map-as ] map
] map ;

: longest-gof-pattern ( n -- game )
all-NxN-games [ game-sequence ] map [ length ] supremum-by but-last ;

Einige Zeitstatistiken:

IN: longest-gof-pattern [ 3 longest-gof-pattern ] time dup length . . 
Running time: 0.08850873500000001 seconds

3
{
   { { 1 1 1 } { 0 0 0 } { 0 0 0 } }
   { { 1 1 1 } { 1 1 1 } { 1 1 1 } }
   { { 0 0 0 } { 0 0 0 } { 0 0 0 } }
}

IN: longest-gof-pattern [ 4 longest-gof-pattern ] time dup length . . 
Running time: 49.667698828 seconds

10
{
  { { 0 1 1 0 } { 0 1 0 0 } { 0 1 0 0 } { 1 1 0 1 } }
  { { 0 1 1 0 } { 0 1 0 0 } { 0 1 0 0 } { 0 0 0 1 } }
  { { 0 1 1 0 } { 0 1 0 0 } { 0 0 1 0 } { 1 1 0 0 } }
  { { 0 1 1 0 } { 0 1 0 0 } { 0 0 1 0 } { 0 0 0 1 } }
  { { 0 1 1 0 } { 0 1 0 0 } { 0 0 1 0 } { 1 1 0 1 } }
  { { 0 1 1 0 } { 0 1 0 0 } { 0 0 1 1 } { 0 0 0 1 } }
  { { 0 1 0 1 } { 0 1 0 1 } { 0 0 1 1 } { 1 1 0 1 } }
  { { 1 1 0 1 } { 1 1 0 1 } { 0 0 0 0 } { 1 1 0 0 } }
  { { 1 1 0 1 } { 1 1 0 1 } { 0 0 1 1 } { 1 1 1 1 } }
  { { 0 0 0 0 } { 0 0 0 0 } { 0 0 0 0 } { 0 0 0 0 } }
}

Beim Testen von 5 stürzte die REPL ab. Hmph. Der ineffizienteste Teil des Programms ist wahrscheinlich die Funktionsspielsequenz. Vielleicht kann ich es später verbessern.

Matthew Rolph
quelle
Cool! Ich denke, Ihre Ausgabe ist 1 zu groß, für 3x3-Muster hat die längste nicht wiederholende Sequenz die Länge 3, nicht 4 ...
Per Alexandersson