Aşağıdaki olası çözüm yaklaşımlarını görüyorum:
- Ağır teori. Torus'ta Yaşam hakkında bazı literatür olduğunu biliyorum, ama çok fazla okumadım.
- Kaba kuvvet ileri: olası her kart için puanını kontrol edin. Bu, Matthew ve Keith'in yaklaşımlarının yaptığı şeydir, ancak Keith, kontrol etmek için tahta sayısını 4 faktör azaltıyor.
- Optimizasyon: kanonik temsil. Bir tahtanın puanını değerlendirmek için gerekenden çok daha hızlı kanonik temsilde olup olmadığını kontrol edebilirsek, yaklaşık 8N ^ 2'lik bir faktörün hızlanmasını elde ederiz. (Daha küçük denklik sınıflarıyla kısmi yaklaşımlar da vardır).
- Optimizasyon: DP. Her bir tahta için skoru önbelleğe alın, böylece yakınlaşana kadar veya ayrılıncaya kadar yürümek yerine daha önce gördüğümüz bir tahta bulana kadar yürürüz. Prensipte bu, ortalama skor / döngü uzunluğunun (belki 20 veya daha fazla) bir faktörüne hız kazandıracaktır, ancak pratikte büyük olasılıkla çok değişiyoruz. Örneğin, N = 6 için 2 ^ 36 skor için kapasiteye ihtiyacımız var, bu da skor başına bir baytta 16GB ve rastgele erişime ihtiyacımız var, bu yüzden iyi önbellek yeri bekleyemeyiz.
- İkisini birleştirin. N = 6 için, tam kanonik temsil DP önbelleğini yaklaşık 60 mega puana indirmemize izin verecektir. Bu umut verici bir yaklaşım.
- Kaba kuvvet geriye doğru. Bu başlangıçta tuhaf görünüyor, ancak hareketsiz yaşamları kolayca bulabileceğimizi ve
Next(board)
işlevi kolayca tersine çevirebileceğimizi varsayarsak, umduğum kadar olmasa da bazı faydaları olduğunu görüyoruz.
- Tahta panoları ile hiç uğraşmıyoruz. Tasarruf pek değil, çünkü oldukça nadirdirler.
- Tüm kartlar için puan depolamamız gerekmiyor, bu nedenle ileri DP yaklaşımından daha az bellek basıncı olmalı.
- Geriye doğru çalışmak, literatürde gördüğüm bir tekniği, hala yaşamları numaralandırma bağlamında değiştirerek oldukça kolaydır. Her sütuna alfabedeki bir harf gibi davranıp, ardından üç harflik bir dizinin gelecek nesilde ortadaki diziyi belirlediğini gözlemleyerek çalışır. Hala hayatları numaralandırmaya paralel olarak o kadar yakın ki, onları sadece biraz garip bir yönteme dönüştürdüm,
Prev2
.
- Hareketsiz yaşamları sadece kanonikleştirebiliriz ve çok az maliyetle 8N ^ 2 hızlandırma gibi bir şey elde edebiliriz. Bununla birlikte, ampirik olarak, her adımda kanonikleşirsek, dikkate alınan kurul sayısında hala büyük bir azalma elde ediyoruz.
- Şaşırtıcı derecede yüksek bir tahta oranı 2 veya 3 puana sahiptir, bu nedenle hala hafıza basıncı vardır. Önceki nesli inşa etmek ve sonra kanonlaşmak yerine kanonikleşmeyi gerekli buldum. Önce genişlik arama yerine derinlik ilkesi yaparak bellek kullanımını azaltmak ilginç olabilir, ancak bunu yığının üzerine taşmadan ve gereksiz hesaplamalar yapmadan yapmak önceki panoları numaralandırmak için bir rutin / devam yaklaşımı gerektirir.
Mikro-optimizasyonun Keith'in kodunu yakalamama izin vereceğini sanmıyorum, ancak ilgi uğruna sahip olduğum şeyi göndereceğim. Bu, N = 5'i Mono 2.4 veya .Net (PLINQ olmadan) kullanan bir 2GHz makinede yaklaşık bir dakika içinde ve PLINQ kullanarak yaklaşık 20 saniye içinde çözer; N = 6 saatlerce çalışır.
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));
}
}
}