Sei tu il solo? (Derivato di Mastermind)


15

Ne ho una dura per te!

La mia ragazza ha recentemente incontrato un nuovo spettacolo su MTV (USA). È uno spettacolo terribile, e tutti sono trasandati, ma il "gioco" è piuttosto interessante. Da Wikipedia:

Sei tu il solo? segue 20 persone che vivono insieme alle Hawaii per trovare la loro coppia perfetta. Se i 10 uomini e le 10 donne sono in grado di scegliere correttamente tutte e dieci le partite perfette in dieci settimane, guadagneranno $ 1 milione da dividere tra loro.

Ora per la parte di gioco (anche da Wikipedia):

Ogni episodio del cast si accoppierà con chi credono che la loro partita perfetta sia competere in una sfida. I vincitori della sfida avranno un appuntamento e avranno la possibilità di testare la loro partita nello stand della verità. I membri del cast sceglieranno una delle coppie vincenti per andare allo stand della verità per determinare se sono una partita perfetta o meno. Questo è l'unico modo per confermare le partite. Ogni episodio termina con una cerimonia di abbinamento in cui alle coppie verrà detto quante partite perfette hanno, ma non quali partite sono corrette.

TL; DR: si tratta di un derivato di Mastermind (M (10,10) per essere specifici). Le regole del gioco sono le seguenti:

  1. Inizi con 2 set da 10, chiamiamoli Set A: {A, B, C, D, E, F, G, H, I, J} e Set 2: {1,2,3,4,5, 6,7,8,9,10}

  2. Il computer crea una soluzione (non visibile all'utente) sotto forma di {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10}, in cui i membri nell'insieme A vengono mappati da 1 a 1 per impostare 2. Un altro esempio di soluzione potrebbe essere {A2, B5, C10, D8, E1, F7, G6, H4, I9, J3}.

  3. Prima del tuo primo turno, ti viene chiesto se una singola coppia particolare a tua scelta è corretta. La tua domanda sarebbe sotto forma di {A1} (ad es. {C8}) e riceverai un 1 (che significa corretto) o 0 (che significa che la tua ipotesi non è corretta).

  4. Il tuo primo turno reale. Fai la tua prima ipotesi sotto forma di {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10} o qualsiasi permutazione di tua scelta. La tua ipotesi non può contenere multipli di nessun elemento, ovvero un'ipotesi di {A1, A2, A3, A4, A5, B6, B7, B8, B9, B10} NON è un'ipotesi valida. Dopo ogni turno, il computer ti dice il numero di corrispondenze corrette, ma NON quali corrispondenze sono corrette.

  5. Ripeti i passaggi 3 e 4 fino a quando tutte le partite non sono corrette (ovvero una risposta di 10) o fino a quando le tue 10 mosse non aumentano (a seconda di quale delle due fasi è più veloce). Se lo risolvi prima o durante il tuo decimo turno, vinci $ 1 milione. Altrimenti, perdi e alcune persone (o lettere e numeri) vanno a casa da sole per passare l'eternità con i loro 10 gatti.

Questo NON è un contest di codice più breve. La persona che sarà in grado di risolvere una corrispondenza casuale nel numero minimo medio di ipotesi sarà il vincitore. Probabilmente anche il gioco intelligente e la velocità di calcolo verranno considerati. Suppongo che il numero medio di turni sarà quasi sicuramente maggiore di 10, quindi le probabilità che tu vinca il premio da $ 1 milione (presumibilmente pagato da MTV, non da me) sono scarse. Proprio come impossibile è per il cast di vincere il primo premio?

Nota: non è necessario inserirlo nel formato {A1, B2, ...}. Ho semplicemente usato quel modulo nella domanda per chiarire assolutamente qual è il puzzle. Se non lo metti in questo modulo, spiega semplicemente come chiamarlo.

In bocca al lupo!


3
Se si desidera che la persona che può risolvere nelle congetture almeno media di vincere, perché non fare che i criteri vincenti? Non vedo alcun motivo per cui questo dovrebbe essere un concorso di popolarità quando una condizione di vittoria perfettamente valida ci sta fissando in faccia.
Geobits il

Per quanto posso dire, la domanda non ha nulla a che fare con la riproduzione ottimale di Mastermind. Richiede un gioco che consenta a un utente di giocarci.
feersum

1
Quindi pop-contest non è il tag per questo.

1
@ hosch250 Criterio aggiornato per il vincitore
dberm22

2
7 voti positivi, 2 preferiti e nessuna risposta. Sapevo che era difficile!
dberm22,

Risposte:


6

Python 2 (correre più veloce se eseguito usando Pypy)

Si ritiene che indovini quasi sempre l'abbinamento corretto in 10 round o inferiore

Il mio algoritmo è tratto dalla mia risposta per mastermind come mio hobby (vedi in Ideone ). L'idea è quella di trovare l'ipotesi che minimizzi il numero di possibilità rimaste nel caso peggiore. Il mio algoritmo sotto brutale lo costringe, ma per risparmiare tempo, seleziona ipotesi casuali se il numero di possibilità rimaste è maggiore di RANDOM_THRESHOLD. Puoi giocare con questo parametro per velocizzare le cose o vedere prestazioni migliori.

L'algoritmo è piuttosto lento, in media 10 secondi per una corsa se eseguito usando Pypy (se usando il normale interprete CPython è di circa 30 secondi) quindi non posso testarlo su tutte le permutazioni. Ma le prestazioni sono abbastanza buone, dopo circa 30 test non ho visto nessun caso in cui non riesca a trovare l'accoppiamento corretto in 10 round o meno.

Ad ogni modo, se questo è usato nello spettacolo della vita reale, ha un sacco di tempo prima del prossimo round (una settimana?), Quindi questo algoritmo può essere usato nella vita reale = D

Quindi penso che sia sicuro supporre che in media questo troverà gli abbinamenti corretti in 10 ipotesi o meno.

Provate voi stessi. Potrei migliorare la velocità nei prossimi giorni (EDIT: sembra difficile migliorare ulteriormente, quindi lascerò il codice così com'è. Ho provato a fare solo pick casuali, ma anche a size=7, fallisce in 3 dei 5040 casi , così ho deciso di mantenere il metodo più intelligente). Puoi eseguirlo come:

pypy are_you_the_one.py 10

Oppure, se vuoi solo vedere come funziona, inserisci un numero più piccolo (in modo che funzioni più velocemente)

Per eseguire un test completo (avviso: ci vorrà molto tempo per size> 7), inserire un numero negativo.

Test completo per size=7(completato in 2m 32s):

...
(6, 5, 4, 1, 3, 2, 0): 5 ipotesi
(6, 5, 4, 2, 0, 1, 3): 5 ipotesi
(6, 5, 4, 2, 0, 3, 1): 4 ipotesi
(6, 5, 4, 2, 1, 0, 3): 5 ipotesi
(6, 5, 4, 2, 1, 3, 0): 6 ipotesi
(6, 5, 4, 2, 3, 0, 1): 6 ipotesi
(6, 5, 4, 2, 3, 1, 0): 6 ipotesi
(6, 5, 4, 3, 0, 1, 2): 6 ipotesi
(6, 5, 4, 3, 0, 2, 1): 3 ipotesi
(6, 5, 4, 3, 1, 0, 2): 7 ipotesi
(6, 5, 4, 3, 1, 2, 0): 7 ipotesi
(6, 5, 4, 3, 2, 0, 1): 4 ipotesi
(6, 5, 4, 3, 2, 1, 0): 7 ipotesi
Conteggio medio: 5,05
Conteggio massimo: 7
Conteggio minimo: 1
Numero successo: 5040

Se RANDOM_THRESHOLDe CLEVER_THRESHOLDentrambi sono impostati su un valore molto alto (come 50000), costringerà l'algoritmo a trovare l'ipotesi ottimale che minimizza il numero di possibilità nel caso peggiore. Questo è molto lento, ma molto potente. Ad esempio, eseguendolo su size=6asserisce che può trovare gli accoppiamenti corretti in massimo 5 round.

Sebbene la media sia più alta rispetto all'utilizzo dell'approssimazione (che è in media 4,11 round), ma ha sempre successo, ancora di più con un round rimasto a disposizione. Ciò rafforza ulteriormente la nostra ipotesi che size=10, quando , dovrebbe quasi sempre trovare gli accoppiamenti corretti in 10 round o meno.

Il risultato (completato in 3m 9s):

(5, 4, 2, 1, 0, 3): 5 ipotesi
(5, 4, 2, 1, 3, 0): 5 ipotesi
(5, 4, 2, 3, 0, 1): 4 ipotesi
(5, 4, 2, 3, 1, 0): 4 ipotesi
(5, 4, 3, 0, 1, 2): 5 ipotesi
(5, 4, 3, 0, 2, 1): 5 ipotesi
(5, 4, 3, 1, 0, 2): 5 ipotesi
(5, 4, 3, 1, 2, 0): 5 ipotesi
(5, 4, 3, 2, 0, 1): 5 ipotesi
(5, 4, 3, 2, 1, 0): 5 ipotesi
Conteggio medio: 4,41
Conteggio massimo: 5
Conteggio minimo: 1
Numero successo: 720

Il codice.

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)

Questo è davvero incredibile. L'ho eseguito altre 100 volte e non ci sono ancora 10 tentativi per trovare la soluzione. Ho visto un paio di 10 e persino un paio di 6. (Dici di non aver visto nessun caso in cui non riesca a trovare l'accoppiamento corretto in 10 round. Ciò dovrebbe probabilmente dire "in 10 o meno round", ma questa è solo una semantica.) È fantastico! Suppongo che il tuo valore lambda sia una sorta di misurazione dell'entropia che ti consente di fare l'ipotesi ottimale, ma non vedo come o dove sia impostato. Sono solo io ad essere denso, non un'accusa al tuo programma. Lavoro incredibile!
dberm22,

Sta cercando di ridurre al minimo il numero di possibilità rimaste nel caso peggiore (la len(remove_perms ...)parte). E sì, intendevo in <= 10 round =). A proposito nel codice sopra l'ipotesi davvero ottimale non viene mai fatta, dal momento che ho messo CLEVER_THRESHOLD=0, il che significa che proverà solo a indovinare dall'elenco delle possibilità, anche se l'ipotesi ottimale potrebbe essere al di fuori di questo set. Ma ho deciso di disabilitarlo per risparmiare tempo. Ho aggiunto il test completo per size=7, dimostrando che ha sempre successo.
solo il

1
Ho eseguito il tuo codice durante la notte con 'Clever Threshold = 0' (a partire da (9,8,7,6,5,4,3,2,1,0) e lavorando all'indietro attraverso tutte le permutazioni). Sono solo 2050 fino ad ora, ma ci sono stati 13 casi in cui ha preso 11 turni. Stampa campione - (9, 8, 7, 4, 0, 6, 3, 2, 1, 5): 9 ipotesi, conteggio medio: 8.29, conteggio massimo: 11, conteggio minimo: 4, successo con numeri: 2037, num valutato: 2050. Se "Clever Threshold" fosse impostato correttamente, scommetto che quegli 11 diventerebbero 10. Tuttavia, in media, 8.3 è piuttosto magnifico.
dberm22,

@ dberm22: Sì, grazie per aver eseguito questo lento algoritmo durante la notte! Ho eseguito il test completo size=8e ho scoperto che l'ultima versione ha solo 40308 successi (anziché 40320) se si utilizza questa impostazione. Ma questo è ancora il tasso di successo del 99,97%! Immagino che possiamo far fallire l'organizzatore di programmi TV.
solo il

3

CJam -19 giri- Strategia di un idiota

Questa non è una risposta seria ma una dimostrazione. Questa è una soluzione di un idiota in cui non tiene conto del numero di informazioni sugli accoppiamenti corretti fornite dalla seconda parte del turno. Con abbinamenti completamente casuali, ci vogliono in media 27 settimane. Questa risposta è insoddisfacente come ho detto, ma indica che le probabilità per un gruppo di intellegenti (molto più intellegente di questo programma) probabilmente non sono sottili come ci si potrebbe aspettare. Gli algoritmi più intellegenti che ho scritto, tuttavia richiedono molto più tempo per essere eseguito, in modo che io possa effettivamente ottenere risposte da essi.

Aggiornamento: il codice seguente è stato aggiornato per utilizzare lo stato che dovrebbe rimuovere quelli che non funzionano se gli unici corretti sono quelli che già sapevamo essere corretti. È stato anche modificato per mostrare il mio generatore di "risposta corretta" casuale. Il risultato medio è ora solo di 19. È ancora una soluzione stupida ma è leggermente migliore della precedente.

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";

Nota anche: la gestione degli errori sciatta è perché è più facile programmare un metodo più intelligente.
Kaine,

+1 per essere abbastanza coraggioso da implementare una soluzione. In realtà sono piuttosto scioccato dal fatto che occorrono in media solo 27 turni per indovinare la soluzione corretta. Suppongo che, come indovini correttamente, le corrispondenze successive siano esponenzialmente (bene, in modo fattoriale) più facili da trovare. Grazie! Sarei interessato a vedere se qualcuno può ottenere meno di 10. Mi hai dato speranza!
dberm22,

Se 27 è sorprendente, guarda quello! A parte gli scherzi, penso che sia possibile una soluzione che garantisca 10 o almeno la ottenga in media. Non riesco proprio a far funzionare un tale algoritmo in un lasso di tempo ragionevole.
Kaine,

19 ... Sono ancora più scioccato. Solo una domanda però ... nel tuo programma, in cui dici "Indovina un numero per il primo sconosciuto. Se a destra imposta la coppia; altrimenti lo cancella". Se è sbagliato ... dovresti aggiungerlo all'elenco delle partite che conosci non sono corrette, quindi non indovinarlo di nuovo (né nella permutazione, né come ipotesi separata).
dberm22,

Significa cancellarlo dall'elenco delle possibilità; Ho un elenco di quelli possibili, non un elenco di quelli impossibili. Oh, e ho dimenticato di menzionare che questo ha il maschio essere in posizione nell'array e la femmina è numeri 0-9 (o viceversa). Userei il formato A5 B2 ecc. Se fosse una presentazione più seria.
Kaine,

3

Versione C ++ multi-thread veloce

So che è passato un po 'di tempo da quando questo thread era attivo, ma ho una bella aggiunta da condividere: ecco un'implementazione in C ++ dell'algoritmo minimax per Are You The One? , che utilizza il multi-threading per accelerare la valutazione di ogni possibile ipotesi.

Questa versione è molto più veloce della versione Python (oltre 100 volte più veloce quando la versione originale di Python è impostata al massimo RANDOM_THRESHOLDe CLEVER_THRESHOLD). Non utilizza alcuna ipotesi casuale, ma piuttosto valuta tutte le permutazioni e presenta come ipotesi la permutazione che elimina il maggior numero di soluzioni possibili (data la risposta nel caso peggiore).

Per i giochi più piccoli, la chiamata "ayto -n" eseguirà il gioco su tutte le n! possibili corrispondenze nascoste e ti fornirà un breve riepilogo dei risultati alla fine.

Dal momento che è ancora intrattabile valutare tutti e 10! possibili corrispondenze nascoste, se si chiama "ayto 10", ad esempio, il simulatore effettua le prime tre ipotesi fisse, quindi esegue minimox per scegliere la sua ipotesi e presume che gli sia stata data la valutazione nel caso peggiore. Questo ci conduce lungo un "percorso nel peggiore dei casi" verso un vettore nascosto che presumibilmente è nella classe dei vettori che considera l'algoritmo un numero massimo di ipotesi da identificare. Questa congettura non è stata testata.

Fino a n = 9 , non c'è stata una simulazione che ha richiesto più di n giri per risolversi.

Per testarlo tu stesso, una compilazione di esempio sarebbe la seguente:

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

Ecco un piccolo esempio con output:

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

Codice

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

L'ho spostato in Are You The One? su GitHub con codice aggiornato e più veloce.
Chris Chute,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.