Você é o único? (Derivada Mastermind)


15

Eu tenho uma pergunta difícil para você!

Minha namorada recentemente se deparou com um novo programa na MTV (EUA). É um show terrível, e todos são inúteis, mas o "jogo" é bem interessante. Da Wikipedia:

Você é o único? segue 20 pessoas que vivem juntas no Havaí para encontrar a combinação perfeita. Se os 10 homens e 10 mulheres puderem escolher corretamente todas as dez combinações perfeitas em dez semanas, eles ganharão US $ 1 milhão para dividir entre eles.

Agora, para a parte do jogo (também da Wikipedia):

Cada episódio que o elenco irá emparelhar com quem eles acreditam que seu par perfeito é competir em um desafio. Os vencedores do desafio sairão em um encontro e terão a chance de testar sua partida no estande da verdade. Os membros do elenco escolherão um dos casais vencedores para ir ao estande da verdade para determinar se eles são uma combinação perfeita ou não. Esta é a única maneira de confirmar correspondências. Cada episódio termina com uma cerimônia de correspondência, onde os casais serão informados de quantas combinações perfeitas elas têm, mas não quais delas estão corretas.

TL; DR: Este é um derivado do Mastermind (M (10,10) para ser específico). As regras do jogo são as seguintes:

  1. Você começa com 2 conjuntos de 10, vamos chamá-los de Conjunto A: {A, B, C, D, E, F, G, H, I, J} e Conjunto 2: {1,2,3,4,5, 6,7,8,9,10}

  2. O computador cria uma solução (não visível para você) na forma de {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10}, em que os membros do conjunto A são mapeados 1 para 1 para definir 2. Outro exemplo de solução pode ser {A2, B5, C10, D8, E1, F7, G6, H4, I9, J3}.

  3. Antes do seu primeiro turno, você pergunta se um único par específico de sua escolha está correto. Sua pergunta seria na forma de {A1} (por exemplo, {C8}) e você receberá 1 (significando correto) ou 0 (significando que seu palpite está incorreto).

  4. Sua primeira vez real. Você faz seu primeiro palpite na forma de {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10} ou qualquer permutação de sua escolha. Sua suposição não pode conter múltiplos de nenhum item, ou seja, uma suposição de {A1, A2, A3, A4, A5, B6, B7, B8, B9, B10} NÃO é válida. Após cada turno, o computador informa o número de correspondências corretas, mas NÃO as correspondências corretas.

  5. Repita as etapas 3 e 4 até acertar todas as correspondências (por exemplo, uma resposta de 10) ou até que seus 10 movimentos subam (o que ocorrer primeiro). Se você resolver isso antes ou no seu 10º turno, você ganha $ 1 milhão. Caso contrário, você perde e algumas pessoas (ou letras e números) vão para casa sozinhas para passar a eternidade com seus 10 gatos.

Este NÃO é um concurso de código mais curto. A pessoa que conseguir resolver uma correspondência aleatória no menor número médio de palpites será o vencedor. Provavelmente, a jogabilidade inteligente e a velocidade de cálculo também serão consideradas. Estou assumindo que o número médio de turnos será quase certamente maior que 10, então as chances de você ganhar o prêmio de US $ 1 milhão (presumivelmente pago pela MTV, não eu) são pequenas. Assim como impossível é para o elenco de ganhar o grande prêmio?

Nota: Colocá-lo no formato {A1, B2, ...} não é necessariamente necessário. Simplesmente usei esse formulário na pergunta para deixar absolutamente claro o que é o quebra-cabeça. Se você não o colocar neste formulário, explique como chamá-lo.

Boa sorte!


3
Se você quer que a pessoa que pode resolvê-lo com menos hipóteses médicas ganhe, por que não fazer disso os critérios de vitória? Não vejo motivo para que este seja um concurso de popularidade quando uma condição de vitória perfeitamente válida está nos encarando.
Geobits

Tanto quanto posso dizer, a pergunta não tem nada a ver com a reprodução ideal de Mastermind. Ele pede um jogo que permita ao usuário jogá-lo.
feersum

1
Então pop-contest não é a etiqueta para isso.

1
@ hosch250 Critério atualizado para vencedor
dberm22

2
7 votos positivos, 2 favoritos e nenhuma resposta. Eu sabia que era difícil!
precisa saber é o seguinte

Respostas:


6

Python 2 (execute mais rápido se for executado usando Pypy)

Acredita-se que quase sempre adivinhe o emparelhamento correto em 10 rodadas ou menos

Meu algoritmo é retirado da minha resposta para o cérebro como meu hobby (veja em Ideone ). A idéia é encontrar um palpite que minimize o número de possibilidades restantes no pior caso. Meu algoritmo abaixo apenas a força bruta, mas para economizar tempo, basta escolher palpites aleatórios se o número de possibilidades restantes for maior que RANDOM_THRESHOLD. Você pode brincar com esse parâmetro para acelerar as coisas ou obter um melhor desempenho.

O algoritmo é bastante lento, em média 10s para uma execução, se executado com Pypy (se o interpretador CPython normal estiver em torno de 30s), então não posso testá-lo em todas as permutações. Mas o desempenho é muito bom, depois de cerca de 30 testes, não vi nenhum caso em que ele não encontre o emparelhamento correto em 10 rodadas ou menos.

De qualquer forma, se isso for usado no programa da vida real, ele terá bastante tempo antes da próxima rodada (uma semana?) Para que esse algoritmo possa ser usado na vida real = D

Portanto, acho que é seguro supor que, em média, este ache os pares corretos em 10 palpites ou menos.

Tente você mesmo. Talvez eu melhore a velocidade nos próximos dias (EDIT: parece difícil melhorar ainda mais, então deixarei o código como está. Tentei apenas fazer uma escolha aleatória, mas mesmo assim size=7, ele falha em 3 dos 5040 casos , então eu decidi manter o método mais inteligente). Você pode executá-lo como:

pypy are_you_the_one.py 10

Ou, se você quiser apenas ver como funciona, insira um número menor (para que seja mais rápido)

Para executar um teste completo (aviso: levará muito tempo para size> 7), coloque um número negativo.

Teste completo para size=7(concluído em 2m 32s):

...
(6, 5, 4, 1, 3, 2, 0): 5 palpites
(6, 5, 4, 2, 0, 1, 3): 5 palpites
(6, 5, 4, 2, 0, 3, 1): 4 palpites
(6, 5, 4, 2, 1, 0, 3): 5 palpites
(6, 5, 4, 2, 1, 3, 0): 6 palpites
(6, 5, 4, 2, 3, 0, 1): 6 palpites
(6, 5, 4, 2, 3, 1, 0): 6 palpites
(6, 5, 4, 3, 0, 1, 2): 6 palpites
(6, 5, 4, 3, 0, 2, 1): 3 palpites
(6, 5, 4, 3, 1, 0, 2): 7 palpites
(6, 5, 4, 3, 1, 2, 0): 7 palpites
(6, 5, 4, 3, 2, 0, 1): 4 palpites
(6, 5, 4, 3, 2, 1, 0): 7 palpites
Contagem média: 5.05
Contagem máxima: 7
Contagem mínima: 1
Num sucesso: 5040

Se RANDOM_THRESHOLDe CLEVER_THRESHOLDestiverem ambos definidos com um valor muito alto (como 50000), forçará o algoritmo a encontrar a estimativa ideal que minimiza o número de possibilidades no pior caso. Isso é muito lento, mas muito poderoso. Por exemplo, executá-lo size=6afirma que ele pode encontrar os pares corretos em no máximo 5 rodadas.

Embora a média seja mais alta em comparação ao uso da aproximação (que é média de 4,11 rodadas), mas sempre é bem-sucedida, ainda mais com uma rodada sobrando. Isso reforça ainda mais nossa hipótese de que size=10, quando , quase sempre deve encontrar os pares corretos em 10 rodadas ou menos.

O resultado (concluído em 3m 9s):

(5, 4, 2, 1, 0, 3): 5 palpites
(5, 4, 2, 1, 3, 0): 5 palpites
(5, 4, 2, 3, 0, 1): 4 palpites
(5, 4, 2, 3, 1, 0): 4 palpites
(5, 4, 3, 0, 1, 2): 5 palpites
(5, 4, 3, 0, 2, 1): 5 palpites
(5, 4, 3, 1, 0, 2): 5 palpites
(5, 4, 3, 1, 2, 0): 5 palpites
(5, 4, 3, 2, 0, 1): 5 palpites
(5, 4, 3, 2, 1, 0): 5 palpites
Contagem média: 4.41
Contagem máxima: 5
Contagem mínima: 1
Num sucesso: 720

O código.

from itertools import permutations, combinations
import random, sys
from collections import Counter

INTERACTIVE = False
ORIG_PERMS = []
RANDOM_THRESHOLD = 100
CLEVER_THRESHOLD = 0

class Unbuffered():
    def __init__(self, stream):
        self.stream = stream

    def write(self, data):
        self.stream.write(data)
        self.stream.flush()

    def __getattr__(self, attr):
        self.stream.getattr(attr)
sys.stdout = Unbuffered(sys.stdout)

def init(size):
    global ORIG_PERMS
    ORIG_PERMS = list(permutations(range(size)))

def evaluate(solution, guess):
    if len(guess) == len(solution):
        cor = 0
        for sol, gss in zip(solution, guess):
            if sol == gss:
                cor += 1
        return cor
    else:
        return 1 if solution[guess[0]] == guess[1] else 0

def remove_perms(perms, evaluation, guess):
    return [perm for perm in perms if evaluate(perm, guess)==evaluation]

def guess_one(possible_perms, guessed_all, count):
    if count == 1:
        return (0,0)
    pairs = Counter()
    for perm in possible_perms:
        for pair in enumerate(perm):
            pairs[pair] += 1
    perm_cnt = len(possible_perms)
    return sorted(pairs.items(), key=lambda x: (abs(perm_cnt-x[1]) if x[1]<perm_cnt else perm_cnt,x[0]) )[0][0]

def guess_all(possible_perms, guessed_all, count):
    size = len(possible_perms[0])
    if count == 1:
        fact = 1
        for i in range(2, size):
            fact *= i
        if len(possible_perms) == fact:
            return tuple(range(size))
        else:
            return tuple([1,0]+range(2,size))
    if len(possible_perms) == 1:
        return possible_perms[0]
    if count < size and len(possible_perms) > RANDOM_THRESHOLD:
        return possible_perms[random.randint(0, len(possible_perms)-1)]
    elif count == size or len(possible_perms) > CLEVER_THRESHOLD:
        (_, next_guess) = min((max(((len(remove_perms(possible_perms, evaluation, next_guess)), next_guess) for evaluation in range(len(next_guess))), key=lambda x: x[0])
                               for next_guess in possible_perms if next_guess not in guessed_all), key=lambda x: x[0])
        return next_guess
    else:
        (_, next_guess) = min((max(((len(remove_perms(possible_perms, evaluation, next_guess)), next_guess) for evaluation in range(len(next_guess))), key=lambda x: x[0])
                               for next_guess in ORIG_PERMS if next_guess not in guessed_all), key=lambda x: x[0])
        return next_guess

def main(size=4):
    if size < 0:
        size = -size
        init(size)
        counts = []
        for solution in ORIG_PERMS:
            count = run_one(solution, False)
            counts.append(count)
            print '%s: %d guesses' % (solution, count)
        sum_count = float(sum(counts))
        print 'Average count: %.2f' % (sum_count/len(counts))
        print 'Max count    : %d' % max(counts)
        print 'Min count    : %d' % min(counts)
        print 'Num success  : %d' % sum(1 for count in counts if count <= size)
    else:
        init(size)
        solution = ORIG_PERMS[random.randint(0,len(ORIG_PERMS)-1)]
        run_one(solution, True)

def run_one(solution, should_print):
    if should_print:
        print solution
    size = len(solution)
    cur_guess = None
    possible_perms = list(ORIG_PERMS)
    count = 0
    guessed_one = []
    guessed_all = []
    while True:
        count += 1
        # Round A, guess one pair
        if should_print:
            print 'Round %dA' % count
        if should_print:
            print 'Num of possibilities: %d' % len(possible_perms)
        cur_guess = guess_one(possible_perms, guessed_all, count)
        if should_print:
            print 'Guess: %s' % str(cur_guess)
        if INTERACTIVE:
            evaluation = int(raw_input('Number of correct pairs: '))
        else:
            evaluation = evaluate(solution, cur_guess)
            if should_print:
                print 'Evaluation: %s' % str(evaluation)
        possible_perms = remove_perms(possible_perms, evaluation, cur_guess)

        # Round B, guess all pairs
        if should_print:
            print 'Round %dB' % count
            print 'Num of possibilities: %d' % len(possible_perms)
        cur_guess = guess_all(possible_perms, guessed_all, count)
        if should_print:
            print 'Guess: %s' % str(cur_guess)
        guessed_all.append(cur_guess)
        if INTERACTIVE:
            evaluation = int(raw_input('Number of correct pairs: '))
        else:
            evaluation = evaluate(solution, cur_guess)
            if should_print: print 'Evaluation: %s' % str(evaluation)
        if evaluation == size:
            if should_print:
                print 'Found %s in %d guesses' % (str(cur_guess), count)
            else:
                return count
            break
        possible_perms = remove_perms(possible_perms, evaluation, cur_guess)

if __name__=='__main__':
    size = 4
    if len(sys.argv) >= 2:
        size = int(sys.argv[1])
        if len(sys.argv) >= 3:
            INTERACTIVE = bool(int(sys.argv[2]))
    main(size)

Isso é realmente incrível. Eu o executei mais 100 vezes e ainda precisa de mais de 10 palpites para encontrar a solução. Eu já vi alguns 10 e até 6. (Você diz que não viu nenhum exemplo em que não possa encontrar o emparelhamento correto abaixo de 10 rodadas. Isso provavelmente deveria dizer "em 10 rodadas ou menos", mas isso é apenas semântica). Isso é fantástico! Eu suponho que seu valor lambda é algum tipo de medida de entropia que permite que você faça o palpite ideal, mas não vejo como ou onde está definido. Este sou apenas eu sendo denso, não uma acusação do seu programa. Trabalho incrível!
precisa

Ele está tentando minimizar o número de possibilidades restantes no pior caso (a len(remove_perms ...)parte). E sim, eu quis dizer em <= 10 rodadas =). Entre no código acima, o palpite realmente ótimo nunca é feito, como eu disse CLEVER_THRESHOLD=0, o que significa que ele apenas tentará adivinhar a partir da lista de possibilidades, embora o palpite ideal possa estar fora deste conjunto. Mas eu decidi desativar isso para economizar tempo. Eu adicionei teste completo para size=7, mostrando que sempre é bem-sucedido.
Justhalf

1
Estive executando seu código durante a noite com 'Clever Threshold = 0' (iniciando em (9,8,7,6,5,4,3,2,1,0) e trabalhando para trás em todas as permutações). Estou apenas em 2050 até agora, mas houve 13 casos em que houve 11 turnos. Impressão de amostra - (9, 8, 7, 4, 0, 6, 3, 2, 1, 5): 9 palpites, Contagem média: 8,29, Contagem máxima: 11, Contagem mínima: 4, Num sucesso: 2037, Num avaliado: 2050. Se 'Clever Threshold' fosse definido corretamente, aposto que esses 11 se tornariam 10. Ainda assim, em média, 8,3 é bastante magnífico.
precisa saber é o seguinte

@ dberm22: Sim, obrigado por executar esse algoritmo lento da noite para o dia! Eu executei o teste completo size=8e descobri que a versão mais recente tem apenas 40308 sucessos (em vez de 40320) se essa configuração for usada. Mas ainda é uma taxa de sucesso de 99,97%! Acho que podemos fazer com que o organizador do programa de TV vá à falência.
justhalf

3

CJam -19 turnos - A estratégia de um idiota

Esta não é uma resposta séria, mas uma demonstração. Esta é a solução de um idiota em que ele não leva em consideração o número de informações corretas de emparelhamento fornecidas a partir da segunda parte do turno. Com pares completamente aleatórios, isso leva em média 27 semanas. Essa resposta é insuficiente, como eu disse, mas indica que as chances de um grupo de intelectuais (muito mais intelectuais que esse programa) provavelmente não são tão pequenas quanto você poderia esperar. Os algoritmos mais intelectuais que eu escrevi, no entanto, levam muito mais tempo para serem executados, para que eu possa realmente obter respostas deles.

Atualização: o código abaixo foi atualizado para usar o estado de que ele deve remover os que não funcionarem se os únicos corretos forem aqueles que já sabíamos que estavam corretos. Também foi editado para mostrar meu gerador aleatório de "resposta correta". O resultado médio agora é de apenas 19. Ainda é uma solução idiota, mas é melhor que a anterior marginalmente.

A,{__,mr=_@@-}A*;]sedS*:Z;

ZS/:i:G;                               "Set the input (goal) to G";
{UU@{G2$==@+\)}%~;}:C;                 "This is the tool to count how many of an array agree with G";
{:Y;1$1$<{Y-}%Yaa+@@)>{Y-}%+}:S;       "for stack A X Y, sets the Xth value in the array to Y";
{:Y;1$1$<2$2$=Y-a+@@)>+}:R;            "for stack A X Y, removes Y from the Xth value in the array";

1:D;                                   "Set turn counter to one. if zero exits loop";

A,]A*                                  "array of arrays that has all possible values for an ordering";

{                                      "start of loop";

_V=(\;_GV=={V\SV):V;}{V\R}?            "Guesses a number for the first unknown. If right sets the pair; else erases it";

_[{(_,_{mr=}{;;11}?:Y\{Y-}%}A*;]_C     "guesses random possible arrangement and determines how many are right, error=11";
\_{+}*45-:Y{Y;{_11={;BY-}{}?}%}{}?\    "error correct by including the missing number";

_V={;V:X>{X\RX):X;}%~LV}{}?            "if all new are wrong, makes sure they aren't guessed again";
_A={Dp0:D;;p;}{D):D;;;}?               "If all are right, prints it an tells loop to exit.  Else increments counter";

D}g                                    "repeat from start of loop";

Observe também: o tratamento de erros desleixados é porque é mais fácil programar que um método mais inteligente.
kaine

+1 por realmente ser corajoso o suficiente para implementar uma solução. Na verdade, estou bastante chocado que leva apenas 27 turnos, em média, para adivinhar a solução correta. Eu diria que, como você adivinha corretamente, as correspondências subsequentes são exponencialmente (bem, fatorialmente) mais fáceis de encontrar. Obrigado! Eu ficaria interessado em ver se alguém pode ter menos de 10. Você me deu esperança!
precisa

Se 27 é surpreendente, olhe isso! Brincadeiras à parte, acho que é possível uma solução que garanta 10 ou pelo menos a obtenha em média. Eu simplesmente não consigo que esse algoritmo funcione em um prazo razoável.
kaine

19 ... Estou ainda mais chocado. Apenas uma pergunta ... no seu programa, em que você diz "Adivinha um número para o primeiro desconhecido. Se a direita definir o par, o outro apagará". Se estiver errado ... você deve adicioná-lo à lista de correspondências que sabe que não estão corretas, para não adivinhar novamente (na permutação ou como uma suposição separada).
precisa saber é o seguinte

Significa apagá-lo da lista de possibilidades; Eu tenho uma lista de possíveis e não de impossíveis. Ah, e eu esqueci de mencionar que isso tem a posição do macho na matriz e o número da fêmea é de 0 a 9 (ou vice-versa). Eu usaria o formato A5 B2 etc. se fosse um envio mais sério.
kaine

3

Versão rápida C ++ multithread

Eu sei que já faz um tempo desde que esse segmento estava ativo, mas tenho uma adição interessante para compartilhar: Aqui está uma implementação em C ++ do algoritmo minimax para Are You The One? , que usa multiencadeamento para acelerar a avaliação de cada palpite possível.

Esta versão é muito mais rápida que a versão do Python (mais de 100x mais rápido quando a versão original do Python está definida como máxima RANDOM_THRESHOLDe CLEVER_THRESHOLD). Ele não usa nenhuma suposição aleatória, mas avalia todas as permutações e submete como suposição a permutação que elimina o maior número de soluções possíveis (dada a resposta do pior caso).

Para jogos menores, chamar "ayto -n" executará o jogo em todos os n! possíveis correspondências ocultas e fornecerá um breve resumo dos resultados no final.

Como ainda é difícil avaliar todos os 10! possíveis correspondências ocultas, se você chamar "ayto 10", por exemplo, o simulador faz suas três primeiras suposições fixas, executa o minimax para escolher sua suposição e assume que recebeu a pior avaliação possível. Isso nos leva a um "caminho do pior caso" para um vetor oculto, presumivelmente na classe de vetores que leva o algoritmo a um número máximo de suposições para identificar. Essa conjectura não foi testada.

Até n = 9 , não houve uma simulação que levou mais de n turnos para resolver.

Para testar você mesmo, um exemplo de compilação seria o seguinte:

g++ -std=c++11 -lpthread -o ayto ayto.cpp

Aqui está um pequeno exemplo com saída:

$ ./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.

Código

/* 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 << ")";
    }
}

Eu mudei isso para Are You The One? no GitHub com código atualizado e mais rápido.
22416 Chris Chute
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.