“窃取最好的东西并加以发展”,
编辑:在当前状态下,此答案不是找到更好的模式,而是找到更好的随机样本。
该答案通过将所有状态枚举为三进制数字(最低有效数字在前)来编码/解码解决方案。解决方案占59.2%:
000000000010010010000000000000000000000000000000000000000000010000010000110000000
000000000010010010000000000111111101111111111111111111000011000010011011000011010
000000000012010011001000000021111111111120111211111111000000000000011010000010000
000011000010022110000000202000000002000000000020000000001010000000011011000011010
020000000010010010001000000111101111111111111111111111010011000011111111010011010
000000000010010010000000000111111111101111111111112111000011010110111011010011011
000000000010010010000000000010000000000000000100002011000000000100011010020010000
000020020010010010000200000111102111111111111111111101000011010010111011000011011
000100000010010010000000000121111111111111111111111111000210000012011011002011010
000000000010010110000000000111112112111111111001111111000010000010011011000011010
000000000010010120000200000111211111111111111111110111110011010011100111010011011
000000000010010010000000000011111111111111111111111111000011010010111211210012020
010000000010010010020100020111110111111111111111111110010111010011011111010111011
002000000010010010000000000111110111111111211111111111001111111111111111111111111
000000000110010010000000000111111111111111211111111111010111011111111111011111011
001000000010010010000000000011111101111111111111110111000011010010111011010011010
001000000010010110000000000111111111111111102111110111010111011111111111011111101
000000000210010010000000000111111111111111111111011111010011010011111111010111011
000000000010010010000000000112111111111111111111101011000000000000011010000010000
000000000010010010000000000111111111111111111111111111000011010010111011010011011
000200000012010010000000000111111111111112111111111111000010000210011211001011010
000000000010010211000002000111111111111111111111111111000001010010111011010011010
000021200010210010000101100111111111111211111110110211010111021111111101010111111
000000000010010010000000000111111111111101111111111111010011010111111111010110021
000200000010010010000000010111111111101111111121112111000210001010011011000011010
000000000010010010000000000111111111111111111111111111210011010021111111010111011
000020000010010010000000000111111111111111111111111111000011010010121011010011012
该答案是使用以下代码从feersum的55.7%演变而来的。此代码需要libop,这是我个人的C ++标头库。安装非常简单,只需git clone https://github.com/orlp/libop
在与保存程序相同的目录中进行即可。我建议使用进行编译g++ -O2 -m64 -march=native -std=c++11 -g
。为了加快开发速度,我还建议通过在上运行上述命令来预编译libop libop/op.h
。
#include <cstdint>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <random>
#include "libop/op.h"
constexpr int MAX_GENERATIONS = 500;
constexpr int NUM_CELLS = 151;
std::mt19937_64 rng;
double worst_best_fitness;
// We use a system with okay-ish memory density. We represent the ternary as a
// 2-bit integer. This means we have 32 ternaries in a uint64_t.
//
// There are 3^7 possible states, requiring 4374 bits. We store this using 69
// uint64_ts, or little over half a kilobyte.
// Turn 7 cells into a state index, by encoding as ternary.
int state_index(const int* cells) {
int idx = 0;
for (int i = 0; i < 7; ++i) {
idx *= 3;
idx += cells[6-i];
}
return idx;
}
// Get/set a ternary by index from a 2-bit-per-ternary encoded uint64_t array.
int get_ternary(const uint64_t* a, size_t idx) {
return (a[idx/32] >> (2*(idx % 32))) & 0x3;
}
void set_ternary(uint64_t* a, size_t idx, int val) {
assert(val < 3);
int shift = 2*(idx % 32);
uint64_t shifted_val = uint64_t(val) << shift;
uint64_t shifted_mask = ~(uint64_t(0x3) << shift);
a[idx/32] = (a[idx/32] & shifted_mask) | shifted_val;
}
struct Rule {
uint64_t data[69];
double cached_fitness;
Rule(const char* init) {
cached_fitness = -1;
for (auto i : op::range(69)) data[i] = 0;
for (auto i : op::range(2187)) set_ternary(data, i, init[i] - '0');
}
double fitness(int num_tests = 1000);
Rule* random_mutation(int num_mutate) const {
auto new_rule = new Rule(*this);
auto r = op::range(2187);
std::vector<int> indices;
op::random_sample(r.begin(), r.end(),
std::back_inserter(indices), num_mutate, rng);
for (auto idx : indices) {
set_ternary(new_rule->data, idx, op::randint(0, 2, rng));
}
new_rule->cached_fitness = -1;
return new_rule;
}
int new_state(const int* cells) const {
return get_ternary(data, state_index(cells));
}
void print_rule() const {
for (auto i : op::range(2187)) {
std::cout << get_ternary(data, i);
if (i % 81 == 80) std::cout << "\n";
}
}
};
struct Automaton {
uint64_t state[5];
int plurality, generation;
Automaton() : generation(0) {
for (auto i : op::range(5)) state[i] = 0;
int strict = 0;
while (strict != 1) {
int votes[3] = {};
for (auto i : op::range(NUM_CELLS)) {
int vote = op::randint(0, 2, rng);
set_ternary(state, i, vote);
votes[vote]++;
}
// Ensure strict plurality.
plurality = std::max_element(votes, votes + 3) - votes;
strict = 0;
for (auto i : op::range(3)) strict += (votes[i] == votes[plurality]);
}
}
void print_state() {
for (int i = 0; i < 151; ++i) std::cout << get_ternary(state, i);
std::cout << "\n";
}
bool concensus_reached() {
int target = get_ternary(state, 0);
for (auto i : op::range(NUM_CELLS)) {
if (get_ternary(state, i) != target) return false;
}
return true;
}
void next_state(const Rule& rule) {
uint64_t new_state[5] = {};
std::vector<int> cells;
for (auto r : op::range(-3, 4)) {
cells.push_back(get_ternary(state, (r + NUM_CELLS) % NUM_CELLS));
}
for (auto i : op::range(NUM_CELLS)) {
set_ternary(new_state, i, rule.new_state(cells.data()));
cells.erase(cells.begin());
cells.push_back(get_ternary(state, (i + 4) % NUM_CELLS));
}
for (auto i : op::range(5)) state[i] = new_state[i];
generation++;
}
};
double Rule::fitness(int num_tests) {
if (cached_fitness == -1) {
cached_fitness = 0;
int num_two = 0;
for (auto test : op::range(num_tests)) {
Automaton a;
while (a.generation < MAX_GENERATIONS && !a.concensus_reached()) {
a.next_state(*this);
}
if (a.generation < MAX_GENERATIONS &&
get_ternary(a.state, 0) == a.plurality &&
a.plurality == 2) num_two++;
cached_fitness += (a.generation < MAX_GENERATIONS &&
get_ternary(a.state, 0) == a.plurality);
if (cached_fitness + (num_tests - test) < worst_best_fitness) break;
}
if (num_two) std::cout << cached_fitness << " " << num_two << "\n";
cached_fitness;
}
return cached_fitness;
}
int main(int argc, char** argv) {
std::random_device rd;
rng.seed(42); // Seed with rd for non-determinism.
const char* base =
"000000000010010010000000000000000000000000000000000000000000000000010000000000000"
"000000000010010010000000000111111111111111111111111111000010000010011011000011010"
"000000000010010010000000000111111111111111111111111111000000000000011010000010000"
"000000000010010010000000000000000000000000000000000000000010000010011011000011010"
"000000000010010010000000000111111111111111111111111111010011010011111111010111011"
"000000000010010010000000000111111111111111111111111111000011010010111011010011011"
"000000000010010010000000000000000000000000000000000000000000000000011010000010000"
"000000000010010010000000000111111111111111111111111111000011010010111011010011011"
"000000000010010010000000000111111111111111111111111111000010000010011011000011010"
"000000000010010010000000000111111111111111111111111111000010000010011011000011010"
"000000000010010010000000000111111111111111111111111111010011010011111111010111011"
"000000000010010010000000000111111111111111111111111111000011010010111011010011010"
"000000000010010010000000000111111111111111111111111111010011010011111111010111011"
"000000000010010010000000000111111111111111111111111111011111111111111111111111111"
"000000000010010010000000000111111111111111111111111111010111011111111111011111111"
"000000000010010010000000000111111111111111111111111111000011010010111011010011010"
"000000000010010010000000000111111111111111111111111111010111011111111111011111111"
"000000000010010010000000000111111111111111111111111111010011010011111111010111011"
"000000000010010010000000000111111111111111111111111111000000000000011010000010000"
"000000000010010010000000000111111111111111111111111111000011010010111011010011011"
"000000000010010010000000000111111111111111111111111111000010000010011011000011010"
"000000000010010010000000000111111111111111111111111111000011010010111011010011010"
"000000000010010010000000000111111111111111111111111111010111011111111111011111111"
"000000000010010010000000000111111111111111111111111111010011010011111111010111011"
"000000000010010010000000000111111111111111111111111111000010000010011011000011010"
"000000000010010010000000000111111111111111111111111111010011010011111111010111011"
"000000000010010010000000000111111111111111111111111111000011010010111011010011012"
;
// Simple best-only.
std::vector<std::unique_ptr<Rule>> best_rules;
best_rules.emplace_back(new Rule(base));
worst_best_fitness = best_rules.back()->fitness();
while (true) {
const auto& base = *op::random_choice(best_rules.begin(), best_rules.end(), rng);
std::unique_ptr<Rule> contender(base->random_mutation(op::randint(0, 100, rng)));
// Sort most fit ones to the beginning.
auto most_fit = [](const std::unique_ptr<Rule>& a, const std::unique_ptr<Rule>& b) {
return a->fitness() > b->fitness();
};
if (contender->fitness() >= best_rules.back()->fitness()) {
std::cout << contender->fitness();
double contender_fitness = contender->fitness();
best_rules.emplace_back(std::move(contender));
std::sort(best_rules.begin(), best_rules.end(), most_fit);
if (best_rules.size() > 5) best_rules.pop_back();
std::cout << " / " << best_rules[0]->fitness() << "\n";
worst_best_fitness = best_rules.back()->fitness();
if (contender_fitness == best_rules.front()->fitness()) {
best_rules.front()->print_rule();
}
}
}
return 0;
}