আমি নিম্নলিখিত সম্ভাব্য সমাধানের পন্থাগুলি দেখছি:
- ভারী তত্ত্ব। আমি জানি একটি লাইনে টরাসে কিছু সাহিত্য রয়েছে তবে আমি এর বেশি পড়িনি।
- বর্বর বাহিনী ফরোয়ার্ড: প্রতিটি সম্ভাব্য বোর্ডের জন্য, এর স্কোর পরীক্ষা করে। মূলত ম্যাথু এবং কিথের পন্থাগুলি এটিই করে, যদিও কিথ 4 এর গুণক দ্বারা পরীক্ষা করার জন্য বোর্ডগুলির সংখ্যা হ্রাস করে।
- অপ্টিমাইজেশন: প্রমিত প্রতিনিধিত্ব। যদি আমরা এটি পরীক্ষা করতে পারি যে কোনও বোর্ডের স্কোরটি মূল্যায়ন করতে তার চেয়ে দ্রুত গতিতে উপস্থাপনা রয়েছে কিনা, আমরা প্রায় 8N ^ 2 এর একটি ফ্যাক্টরটির গতি অর্জন করি। (ছোট সমতুল্য শ্রেণীর সাথেও আংশিক পন্থা রয়েছে)।
- অপ্টিমাইজেশন: ডিপি। প্রতিটি বোর্ডের জন্য স্কোরকে ক্যাশে করুন, যাতে তারা রূপান্তর বা ডাইভার্জ না করা পর্যন্ত কেবল সেগুলি না চালানোর পরিবর্তে আমরা কেবল আগেই দেখেছি এমন বোর্ড না পাওয়া পর্যন্ত আমরা কেবল হাঁটাচলা করি। নীতিগতভাবে এটি গড় স্কোর / চক্র দৈর্ঘ্যের (সম্ভবত 20 বা ততোধিক) দৈর্ঘ্যের একটি উপাদান দ্বারা গতি বাড়িয়ে তুলবে, তবে বাস্তবে আমরা সম্ভবত খুব বেশি অদলবদল করব। উদাহরণস্বরূপ এন = 6 এর জন্য আমাদের 2 ^ 36 স্কোরের জন্য সক্ষমতা প্রয়োজন, যা প্রতি স্কোর বাইটে 16 গিগাবাইট এবং আমাদের এলোমেলো অ্যাক্সেস প্রয়োজন তাই আমরা ভাল ক্যাশে লোকালটি আশা করতে পারি না।
- দুজনকে একত্রিত করুন। এন = For এর জন্য, সম্পূর্ণ আধ্যাত্মিক উপস্থাপনা আমাদের প্রায় 60 মেগা-স্কোর ডিপি ক্যাশে হ্রাস করতে দেয়। এটি একটি প্রতিশ্রুতিবদ্ধ পদ্ধতির।
- পিছনে জোর জোর। এটি প্রথমে অদ্ভুত বলে মনে হয়, তবে আমরা যদি ধরে নিই যে আমরা সহজেই প্রাণবন্ত খুঁজে পেতে পারি এবং আমরা সহজেই
Next(board)
ফাংশনটি উল্টাতে পারি তবে আমরা দেখতে পাচ্ছি যে এর কিছু সুবিধা রয়েছে, যদিও আমি যতটা আশা করি তেমন নয়।
- আমরা ডাইভারিং বোর্ডগুলি মোটেও বিরক্ত করি না। সংরক্ষণের খুব বেশি নয়, কারণ এগুলি বেশ বিরল।
- আমাদের সকল বোর্ডের জন্য স্কোর সংরক্ষণ করার দরকার নেই, সুতরাং ফরোয়ার্ড ডিপি পদ্ধতির চেয়ে মেমরির চাপ কম হওয়া উচিত।
- পিছনের দিকে কাজ করা সাহিত্যে এখনও জীবদ্দশায় গণনার প্রসঙ্গে আমি যে কৌশলটি দেখেছি তার ভিন্নতা দ্বারা বাস্তবে যথেষ্ট সহজ। এটি প্রতিটি কলামকে বর্ণমালার অক্ষর হিসাবে বিবেচনা করে এবং তারপরে পর্যবেক্ষণ করে যে তিনটি বর্ণের ক্রম পরবর্তী প্রজন্মের মধ্যবর্তীটি নির্ধারণ করে। অঙ্কিত স্থিরজীবনের সমান্তরালটি এতটাই নিকটবর্তী যে আমি এগুলিকে কেবলমাত্র কিছুটা বিশ্রী পদ্ধতিতে রিফেক্টর করেছি
Prev2
।
- মনে হতে পারে আমরা স্থির জীবনকে কেবল ক্যানোনিকালাইজ করতে পারি এবং খুব অল্প ব্যয়ের জন্য 8N ^ 2 স্পিড-আপের মতো কিছু পেতে পারি। তবে, পরীক্ষামূলকভাবে আমরা প্রতিটি পদক্ষেপে ক্যানোনিকালাইজেশন করা থাকলে বোর্ড বিবেচিত বোর্ডগুলিতে এখনও একটি বড় হ্রাস পেয়েছি।
- আশ্চর্যজনকভাবে বোর্ডের একটি উচ্চতর অনুপাতের স্কোর 2 বা 3 থাকে, সুতরাং এখনও মেমরির চাপ রয়েছে। আমি পূর্বের প্রজন্মের তৈরির পরে ক্যানোনিকালাইজিংয়ের পরিবর্তে ফ্লাইয়ে ক্যানোনিকালাইজেশন করা প্রয়োজনীয় বলে মনে করেছি। প্রস্থের প্রথম সন্ধানের চেয়ে গভীরতা-প্রথম করার মাধ্যমে মেমরির ব্যবহার হ্রাস করা আকর্ষণীয় হতে পারে তবে স্ট্যাককে উপচে না ফেলে এবং অপ্রয়োজনীয় গণনা না করে পূর্ববর্তী বোর্ডগুলি গণনার জন্য একটি সহ-রুটিন / ধারাবাহিকতা পদ্ধতির প্রয়োজন।
আমি মনে করি না যে মাইক্রো-অপ্টিমাইজেশন আমাকে কীথের কোডটি ধরতে দেবে, তবে আগ্রহের জন্য আমি যা আছে তা পোস্ট করব। এটি মনো 2 2.4 বা। নেট (পিআইএনএলকিউ ব্যতীত) এবং পিএনএলকিউ ব্যবহার করে প্রায় 20 সেকেন্ডে 2GHz মেশিনে প্রায় এক মিনিটের মধ্যে এন = 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));
}
}
}