가능한 해결책은 다음과 같습니다.
- 무거운 이론. 나는 Torus에 관한 Life에 관한 문헌이 있지만, 그 내용을 많이 읽지 않았습니다.
- 무차별 포워드 포워드 : 가능한 모든 보드에 대해 점수를 확인하십시오. Keith는 기본적으로 Matthew와 Keith의 접근 방식이 수행하지만 Keith는 검사 할 보드 수를 4 배로 줄입니다.
- 최적화 : 표준 표현. 보드가 점수를 평가하는 것보다 훨씬 빨리 정식으로 표시되는지 확인할 수 있으면 약 8N ^ 2의 속도가 향상됩니다. (등가 클래스가 더 작은 부분적인 접근 방식도 있습니다).
- 최적화 : DP. 각 보드의 점수를 캐시하여 수렴하거나 발산 할 때까지 걷지 말고 이전에 본 보드를 찾을 때까지 걷습니다. 원칙적으로 이것은 평균 점수 / 사이클 길이 (20 이상)의 요인으로 속도를 높이지만 실제로는 크게 교환 될 가능성이 있습니다. 예를 들어 N = 6의 경우 2 ^ 36 점수에 대한 용량이 필요합니다. 2 ^ 36 점수는 1 바이트 당 16GB이며, 임의의 액세스가 필요하므로 좋은 캐시 위치를 기대할 수 없습니다.
- 둘을 결합하십시오. N = 6의 경우, 전체 표준 표현을 통해 DP 캐시를 약 60 메가-스코어로 줄일 수 있습니다. 이것은 유망한 접근법입니다.
- 거꾸로 힘. 처음에는 이상하게 보이지만 정물을 쉽게 찾을 수 있고
Next(board)
기능을 쉽게 뒤집을 수 있다고 가정하면 원래 원하는 것만 큼 많지는 않지만 몇 가지 이점이 있음을 알 수 있습니다 .
- 우리는 분기 보드를 전혀 신경 쓰지 않습니다. 그것들은 아주 드물기 때문에 절약하지는 않습니다.
- 모든 보드에 대해 점수를 저장할 필요는 없으므로 정방향 DP 접근 방식보다 메모리 압력이 적어야합니다.
- 정물을 열거하는 맥락에서 문헌에서 본 기술을 변화 시켜서 거꾸로 작업하는 것은 실제로 매우 쉽습니다. 각 열을 알파벳의 문자로 취급 한 다음 3 개의 문자 시퀀스가 다음 세대의 중간 문자를 결정한다는 것을 관찰합니다. 정물을 열거하는 것과 비슷한 점이 너무 가까워서 조금 어색한 방법으로 리팩토링했습니다
Prev2
.
- 우리는 정물을 정식화 할 수 있고 적은 비용으로 8N ^ 2 속도 향상과 같은 것을 얻을 수 있습니다. 그러나 경험적으로 우리는 각 단계에서 정식화 할 경우 고려되는 보드 수를 크게 줄입니다.
- 놀랍게도 높은 보드 비율은 2 또는 3의 점수를 가지므로 여전히 메모리 압력이 있습니다. 나는 이전 세대를 건설 한 다음 정규화하기보다는 즉석에서 정규화해야한다는 것을 알았습니다. 너비 우선 검색보다는 깊이 우선을 수행하여 메모리 사용을 줄이는 것이 흥미로울 수 있지만 스택을 오버플로하지 않고 중복 계산을 수행하지 않고 이전 보드를 열거하려면 공동 루틴 / 연속 접근 방식이 필요합니다.
마이크로 최적화가 Keith의 코드를 따라 잡을 수 있다고 생각하지는 않지만 관심을 끌기 위해 내가 가진 것을 게시 할 것입니다. 이렇게하면 Mono 2.4 또는 .Net (PLINQ 없음)을 사용하는 2GHz 시스템에서 약 1 분, PLINQ를 사용하여 약 20 초 동안 N = 5가 해결됩니다. N = 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));
}
}
}