빠른 다중 스레드 C ++ 버전
이 스레드가 활성화 된 이후 오랜 시간이 걸렸지 만 공유 할 멋진 추가 기능이 있습니다. 다음 은 Are You The One 의 minimax 알고리즘에 대한 C ++ 구현입니다 . 멀티 스레딩을 사용하여 가능한 각 추측의 평가 속도를 높입니다.
이 버전은 Python 버전보다 훨씬 빠릅니다 (원래 Python 버전이 최대 RANDOM_THRESHOLD
및 로 설정된 경우 100 배 이상 빠름 CLEVER_THRESHOLD
). 임의의 추측을 사용하지 않고 모든 순열을 평가하고 가능한 가장 많은 수의 솔루션을 제거하는 순열을 추측으로 제출합니다 (최악의 경우 응답).
작은 게임의 경우 "ayto -n"을 호출하면 모든 n에서 게임이 실행됩니다! 숨겨진 일치 항목이 있으며 마지막에 결과를 간략하게 요약합니다.
10 개 모두를 평가하는 것은 여전히 어려운 일입니다! 예를 들어, "ayto 10"을 호출하면 가능한 숨겨진 일치 항목이 시뮬레이터에서 처음 세 가지 추측을 한 다음 minimax를 실행하여 추측을 선택하고 최악의 평가를 받았다고 가정합니다. 이것은 알고리즘을 식별하기 위해 최대 추측 수를 취하는 벡터 클래스에있는 숨겨진 벡터에 대한 "최악의 경로"로 안내합니다. 이 추측은 테스트되지 않았습니다.
최대 9 명 이상 촬영하고있다 시뮬레이션이되지 않았습니다 N을 해결하기집니다.
이것을 직접 테스트하기 위해 컴파일 예는 다음과 같습니다.
g++ -std=c++11 -lpthread -o ayto ayto.cpp
다음은 출력이 포함 된 작은 예입니다.
$ ./ayto -4
Found (0, 1, 2, 3) in 2 guesses.
Found (0, 1, 3, 2) in 3 guesses.
Found (0, 2, 1, 3) in 2 guesses.
Found (0, 2, 3, 1) in 3 guesses.
Found (0, 3, 1, 2) in 2 guesses.
Found (0, 3, 2, 1) in 2 guesses.
Found (1, 0, 2, 3) in 1 guesses.
Found (1, 0, 3, 2) in 3 guesses.
Found (1, 2, 0, 3) in 3 guesses.
Found (1, 2, 3, 0) in 3 guesses.
Found (1, 3, 0, 2) in 3 guesses.
Found (1, 3, 2, 0) in 3 guesses.
Found (2, 0, 1, 3) in 3 guesses.
Found (2, 0, 3, 1) in 3 guesses.
Found (2, 1, 0, 3) in 3 guesses.
Found (2, 1, 3, 0) in 3 guesses.
Found (2, 3, 0, 1) in 3 guesses.
Found (2, 3, 1, 0) in 3 guesses.
Found (3, 0, 1, 2) in 3 guesses.
Found (3, 0, 2, 1) in 3 guesses.
Found (3, 1, 0, 2) in 3 guesses.
Found (3, 1, 2, 0) in 3 guesses.
Found (3, 2, 0, 1) in 3 guesses.
Found (3, 2, 1, 0) in 3 guesses.
***** SUMMARY *****
Avg. Turns: 2.75
Worst Hidden Vector: (0, 1, 3, 2) in 3 turns.
암호
/* Multithreaded Mini-max Solver for MTV's Are You The One? */
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <algorithm>
#include <numeric>
#include <string>
#include <vector>
#include <map>
#include <thread>
#include <cmath>
#define TEN_FACT (3628800)
#define NUM_CHUNKS (8)
using std::cout;
using std::cin;
using std::endl;
using std::vector;
using std::string;
using std::map;
using std::pair;
using std::find;
using std::abs;
using std::atoi;
using std::next_permutation;
using std::max_element;
using std::accumulate;
using std::reverse;
using std::thread;
struct args {
vector<string> *perms;
vector<string> *chunk;
pair<string, int> *cd;
int thread_id;
};
void simulate_game(const string &hidden, map<string, int> &turns_taken,
bool running_all);
bool picmp(const pair<string, int> &p1, const pair<string, int> &p2);
double map_avg(const map<string, int> &mp);
int nrand(int n);
int evaluate(const string &sol, const string &query);
vector<string> remove_perms(vector<string> &perms, int eval, string &query);
pair<string, int> guess_tb(vector<string> &perms, vector<string> &guessed_tb, int turn);
pair<string, int> guess_pm(vector<string> &perms, vector<string> &guessed, int turn);
void make_chunks(vector<string> &orig, vector<vector<string> > &chunks, int n);
string min_candidate(pair<string, int> *candidates, int n);
void get_score(struct args *args);
int wc_response(string &guess, vector<string> &perms);
bool prcmp(pair<int, int> x, pair<int, int> y);
void sequence_print(string s);
struct args **create_args(vector<string> &perms, pair<string, int> *cd, vector<string> &chunk, int thread_id);
vector<string> ORIGPERMS;
int main(int argc, char **argv)
{
int sz;
map<string, int> turns_taken;
const string digits = "0123456789";
bool running_all = false;
if (argc != 2) {
cout << "usage: 'ayto npairs'" << endl;
return 1;
} else {
if ((sz = atoi(argv[1])) < 0) {
sz = -sz;
running_all = true;
}
if (sz < 3 || sz > 10) {
cout << "usage: 'ayto npairs' where 3 <= npairs <= 10" << endl;;
return 1;
}
}
// initialize ORIGPERMS and possible_perms
string range = digits.substr(0, sz);
do {
ORIGPERMS.push_back(range);
} while (next_permutation(range.begin(), range.end()));
if (running_all) {
for (vector<string>::const_iterator it = ORIGPERMS.begin();
it != ORIGPERMS.end(); ++it) {
simulate_game(*it, turns_taken, running_all);
}
cout << "***** SUMMARY *****\n";
cout << "Avg. Turns: " << map_avg(turns_taken) << endl;
pair<string, int> wc = *max_element(turns_taken.begin(),
turns_taken.end(), picmp);
cout << "Worst Hidden Vector: ";
sequence_print(wc.first);
cout << " in " << wc.second << " turns." << endl;
} else {
string hidden = ORIGPERMS[nrand(ORIGPERMS.size())];
simulate_game(hidden, turns_taken, running_all);
}
return 0;
}
// simulate_game: run a single round of AYTO on hidden vector
void simulate_game(const string &hidden, map<string, int> &turns_taken,
bool running_all)
{
vector<string> possible_perms = ORIGPERMS;
pair<string, int> tbguess;
pair<string, int> pmguess;
vector<string> guessed;
vector<string> guessed_tb;
int e;
int sz = hidden.size();
if (!running_all) {
cout << "Running AYTO Simulator on Hidden Vector: ";
sequence_print(hidden);
cout << endl;
}
for (int turn = 1; ; ++turn) {
// stage one: truth booth
if (!running_all) {
cout << "**** Round " << turn << "A ****" << endl;
cout << "Num. Possibilities: " << possible_perms.size() << endl;
}
tbguess = guess_tb(possible_perms, guessed_tb, turn);
if (!running_all) {
cout << "Guess: ";
sequence_print(tbguess.first);
cout << endl;
e = tbguess.second;
cout << "Worst-Case Evaluation: " << e << endl;
} else {
e = evaluate(hidden, tbguess.first);
}
possible_perms = remove_perms(possible_perms, e, tbguess.first);
// stage two: perfect matching
if (!running_all) {
cout << "Round " << turn << "B" << endl;
cout << "Num. Possibilities: " << possible_perms.size() << endl;
}
pmguess = guess_pm(possible_perms, guessed, turn);
if (!running_all) {
cout << "Guess: ";
sequence_print(pmguess.first);
cout << endl;
e = pmguess.second;
cout << "Worst-Case Evaluation: " << e << endl;
} else {
e = evaluate(hidden, pmguess.first);
}
if (e == sz) {
cout << "Found ";
sequence_print(pmguess.first);
cout << " in " << turn << " guesses." << endl;
turns_taken[pmguess.first] = turn;
break;
}
possible_perms = remove_perms(possible_perms, e, pmguess.first);
}
}
// map_avg: returns average int component of a map<string, int> type
double map_avg(const map<string, int> &mp)
{
double sum = 0.0;
for (map<string, int>::const_iterator it = mp.begin();
it != mp.end(); ++it) {
sum += it->second;
}
return sum / mp.size();
}
// picmp: comparison function for pair<string, int> types, via int component
bool picmp(const pair<string, int> &p1, const pair<string, int> &p2)
{
return p1.second < p2.second;
}
// nrand: random integer in range [0, n)
int nrand(int n)
{
srand(time(NULL));
return rand() % n;
}
// evaluate: number of black hits from permutation or truth booth query
int evaluate(const string &sol, const string &query)
{
int hits = 0;
if (sol.size() == query.size()) {
// permutation query
int s = sol.size();
for (int i = 0; i < s; i++) {
if (sol[i] == query[i])
++hits;
}
} else {
// truth booth query
if (sol[atoi(query.substr(0, 1).c_str())] == query[1])
++hits;
}
return hits;
}
// remove_perms: remove solutions that are no longer possible after an eval
vector<string> remove_perms(vector<string> &perms, int eval, string &query)
{
vector<string> new_perms;
for (vector<string>::iterator i = perms.begin(); i != perms.end(); i++) {
if (evaluate(*i, query) == eval) {
new_perms.push_back(*i);
}
}
return new_perms;
}
// guess_tb: guesses best pair (pos, val) to go to the truth booth
pair<string, int> guess_tb(vector<string> &possible_perms,
vector<string> &guessed_tb, int turn)
{
static const string digits = "0123456789";
int n = possible_perms[0].size();
pair<string, int> next_guess;
if (turn == 1) {
next_guess.first = "00";
next_guess.second = 0;
} else if (possible_perms.size() == 1) {
next_guess.first = "0" + possible_perms[0].substr(0, 1);
next_guess.second = 1;
} else {
map<string, double> pair_to_count;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
pair_to_count[digits.substr(i, 1) + digits.substr(j, 1)] = 0;
}
}
// count up the occurrences of each pair in the possible perms
for (vector<string>::iterator p = possible_perms.begin();
p != possible_perms.end(); p++) {
int len = possible_perms[0].size();
for (int i = 0; i < len; i++) {
pair_to_count[digits.substr(i, 1) + (*p).substr(i, 1)] += 1;
}
}
double best_dist = 1;
int perm_cnt = possible_perms.size();
for (map<string, double>::iterator i = pair_to_count.begin();
i != pair_to_count.end(); i++) {
if (find(guessed_tb.begin(), guessed_tb.end(), i->first)
== guessed_tb.end()) {
// hasn't been guessed yet
if (abs(i->second/perm_cnt - .5) < best_dist) {
next_guess.first = i->first;
best_dist = abs(i->second/perm_cnt - .5);
if (i->second / perm_cnt < 0.5) // occurs in < half perms
next_guess.second = 0;
else // occurs in >= half perms
next_guess.second = 1;
}
}
}
}
guessed_tb.push_back(next_guess.first);
return next_guess;
}
// guess_pm: guess a full permutation using minimax
pair<string, int> guess_pm(vector<string> &possible_perms,
vector<string> &guessed, int turn)
{
static const string digits = "0123456789";
pair<string, int> next_guess;
vector<vector<string> > chunks;
int sz = possible_perms[0].size();
// on first turn, we guess "0, 1, ..., n-1" if truth booth was correct
// or "1, 0, ..., n-1" if truth booth was incorrect
if (turn == 1) {
int fact, i;
for (i = 2, fact = 1; i <= sz; fact *= i++)
;
if (possible_perms.size() == fact) {
next_guess.first = digits.substr(0, sz);
next_guess.second = 1;
} else {
next_guess.first = "10" + digits.substr(2, sz - 2);
next_guess.second = 1;
}
} else if (possible_perms.size() == 1) {
next_guess.first = possible_perms[0];
next_guess.second = possible_perms[0].size();
} else {
// run multi-threaded minimax to get next guess
pair<string, int> candidates[NUM_CHUNKS];
vector<thread> jobs;
make_chunks(ORIGPERMS, chunks, NUM_CHUNKS);
struct args **args = create_args(possible_perms, candidates, chunks[0], 0);
for (int j = 0; j < NUM_CHUNKS; j++) {
args[j]->chunk = &(chunks[j]);
args[j]->thread_id = j;
jobs.push_back(thread(get_score, args[j]));
}
for (int j = 0; j < NUM_CHUNKS; j++) {
jobs[j].join();
}
next_guess.first = min_candidate(candidates, NUM_CHUNKS);
next_guess.second = wc_response(next_guess.first, possible_perms);
for (int j = 0; j < NUM_CHUNKS; j++)
free(args[j]);
free(args);
}
guessed.push_back(next_guess.first);
return next_guess;
}
struct args **create_args(vector<string> &perms, pair<string, int> *cd, vector<string> &chunk, int thread_id)
{
struct args **args = (struct args **) malloc(sizeof(struct args*)*NUM_CHUNKS);
assert(args);
for (int i = 0; i < NUM_CHUNKS; i++) {
args[i] = (struct args *) malloc(sizeof(struct args));
assert(args[i]);
args[i]->perms = &perms;
args[i]->cd = cd;
}
return args;
}
// make_chunks: return pointers to n (nearly) equally sized vectors
// from the original vector
void make_chunks(vector<string> &orig, vector<vector<string> > &chunks, int n)
{
int sz = orig.size();
int chunk_sz = sz / n;
int n_with_extra = sz % n;
vector<string>::iterator b = orig.begin();
vector<string>::iterator e;
for (int i = 0; i < n; i++) {
int m = chunk_sz; // size of this chunk
if (n_with_extra) {
++m;
--n_with_extra;
}
e = b + m;
vector<string> subvec(b, e);
chunks.push_back(subvec);
b = e;
}
}
// min_candidate: string with min int from array of pair<string, ints>
string min_candidate(pair<string, int> *candidates, int n)
{
int i, minsofar;
string minstring;
minstring = candidates[0].first;
minsofar = candidates[0].second;
for (i = 1; i < n; ++i) {
if (candidates[i].second < minsofar) {
minsofar = candidates[i].second;
minstring = candidates[i].first;
}
}
return minstring;
}
// get_score: find the maximum number of remaining solutions over all
// possible responses to the query s
// this version takes a chunk and finds the guess with lowest score
// from that chunk
void get_score(struct args *args)
{
// parse the args struct
vector<string> &chunk = *(args->chunk);
vector<string> &perms = *(args->perms);
pair<string, int> *cd = args->cd;
int thread_id = args->thread_id;
typedef vector<string>::const_iterator vec_iter;
int sz = perms[0].size();
pair<string, int> best_guess;
best_guess.second = perms.size();
int wc_num_remaining;
for (vec_iter s = chunk.begin(); s != chunk.end(); ++s) {
vector<int> matches(sz + 1, 0);
for (vec_iter p = perms.begin(); p != perms.end(); ++p) {
++matches[evaluate(*s, *p)];
}
wc_num_remaining = *max_element(matches.begin(), matches.end());
if (wc_num_remaining < best_guess.second) {
best_guess.first = *s;
best_guess.second = wc_num_remaining;
}
}
cd[thread_id] = best_guess;
return;
}
// wc_response: the response to guess that eliminates the least solutions
int wc_response(string &guess, vector<string> &perms)
{
map<int, int> matches_eval;
for (vector<string>::iterator it = perms.begin(); it!=perms.end(); ++it) {
++matches_eval[evaluate(guess, *it)];
}
return max_element(matches_eval.begin(), matches_eval.end(), prcmp)->first;
}
// prcmp: comparison function for pair<int, int> types in map
bool prcmp(pair<int, int> x, pair<int, int> y)
{
return x.second < y.second;
}
void sequence_print(const string s)
{
for (string::const_iterator i = s.begin(); i != s.end(); i++) {
if (i == s.begin())
cout << "(";
cout << *i;
if (i != s.end() - 1)
cout << ", ";
else
cout << ")";
}
}