मैं निम्नलिखित संभव समाधान दृष्टिकोण देखता हूं:
- भारी सिद्धांत। मुझे पता है कि टोरस पर लाइफ पर कुछ साहित्य है, लेकिन मैंने इसे ज्यादा पढ़ा नहीं है।
- बल के आगे की ओर: हर संभव बोर्ड के लिए, उसके स्कोर की जाँच करें। यह मूल रूप से मैथ्यू और कीथ के दृष्टिकोण हैं, हालांकि कीथ 4 के कारक द्वारा जांच करने के लिए बोर्डों की संख्या कम कर देता है।
- अनुकूलन: विहित प्रतिनिधित्व। यदि हम यह जांच कर सकते हैं कि क्या बोर्ड अपने प्रतिनिधित्व का मूल्यांकन करने के लिए कैनोनिकल प्रतिनिधित्व में बहुत तेज है या नहीं, तो हमें लगभग 8N ^ 2 के एक कारक का स्पीड-अप मिलता है। (छोटे समतुल्य वर्गों के साथ आंशिक दृष्टिकोण भी हैं)।
- अनुकूलन: डीपी। प्रत्येक बोर्ड के लिए स्कोर को कैश करें, ताकि उनके माध्यम से चलने के बजाय जब तक वे अभिसरण या विचलन न करें हम तब तक चलते हैं जब तक कि हम एक बोर्ड नहीं पाते हैं जो हमने पहले देखा है। सिद्धांत रूप में यह औसत स्कोर / चक्र की लंबाई (शायद 20 या अधिक) के एक कारक द्वारा गति-अप देगा, लेकिन व्यवहार में हमें भारी स्वैप होने की संभावना है। उदाहरण के लिए N = 6 के लिए हमें 2 ^ 36 स्कोर की क्षमता की आवश्यकता होगी, जो कि प्रति स्कोर बाइट 16GB है, और हमें रैंडम एक्सेस की आवश्यकता है ताकि हम अच्छे कैश लोकल की उम्मीद न कर सकें।
- दोनों को मिलाएं। एन = 6 के लिए, पूर्ण विहित प्रतिनिधित्व हमें डीपी कैश को लगभग 60 मेगा-स्कोर तक कम करने की अनुमति देगा। यह एक आशाजनक दृष्टिकोण है।
- पाशविक बल पीछे की ओर। यह पहली बार में अजीब लगता है, लेकिन अगर हम यह मान लें कि हम आसानी से अभी भी जीवन पा सकते हैं और हम आसानी से
Next(board)
फ़ंक्शन को उलट सकते हैं , तो हम देखते हैं कि इसके कुछ फायदे हैं, हालांकि मूल रूप से मुझे जितनी उम्मीद थी।
- हम बोर्डों को मोड़ने से बिल्कुल भी परेशान नहीं हैं। ज्यादा बचत नहीं, क्योंकि वे काफी दुर्लभ हैं।
- हमें सभी बोर्डों के लिए स्कोर स्टोर करने की आवश्यकता नहीं है, इसलिए आगे के डीपी दृष्टिकोण की तुलना में स्मृति दबाव कम होना चाहिए।
- पीछे की ओर काम करना वास्तव में एक तकनीक को अलग करके काफी आसान है जिसे मैंने साहित्य में अभी भी जीवन की गणना के संदर्भ में देखा था। यह प्रत्येक स्तंभ को एक वर्णमाला में एक अक्षर के रूप में मानकर काम करता है और फिर यह देखता है कि तीन अक्षरों का एक क्रम अगली पीढ़ी में मध्य का निर्धारण करता है। अभी भी उम्र बढ़ने के साथ समानांतर इतना करीब है कि मैं उन्हें एक साथ थोड़ा ही अजीब विधि में एक साथ refactored है
Prev2
।
- ऐसा लगता है कि हम अभी भी अभी भी canonicalise कर सकते हैं, और बहुत कम लागत के लिए 8N ^ 2 स्पीड-अप जैसा कुछ प्राप्त कर सकते हैं। हालाँकि, अनुभवजन्य रूप से हमें अभी भी माना जाता है कि यदि हम प्रत्येक चरण पर कैनोनिकलिज़ करते हैं, तो बोर्ड की संख्या में बड़ी कमी आती है।
- आश्चर्यजनक रूप से उच्च अनुपात में बोर्डों का स्कोर 2 या 3 होता है, इसलिए अभी भी स्मृति दबाव है। मैंने पिछली पीढ़ी के निर्माण और फिर विहितीकरण के बजाय मक्खी पर कैनोनिकलिज़ करना आवश्यक पाया। चौड़ाई-पहली खोज के बजाय गहराई-प्रथम करके मेमोरी उपयोग को कम करना दिलचस्प हो सकता है, लेकिन स्टैक को ओवरफ्लो किए बिना और अनावश्यक गणना किए बिना ऐसा करना पिछले बोर्डों को फिर से शुरू करने के लिए सह-दिनचर्या / निरंतरता दृष्टिकोण की आवश्यकता होती है।
मुझे नहीं लगता कि माइक्रो-ऑप्टिमाइज़ेशन मुझे कीथ कोड के साथ पकड़ बनाने देगा, लेकिन मेरी रुचि के लिए मेरे पास वही होगा जो मेरे पास है। यह मोनो = 2.4 या .Net (PLINQ के बिना) और PLINQ का उपयोग करके लगभग 20 सेकंड में 2GHz मशीन पर एक मिनट के बारे में N = 5 को हल करता है; एन = 6 कई घंटों तक चलता है।
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));
}
}
}