Ти єдина? (Похідний натхненник)


15

У мене важкий для вас!

Моя подруга нещодавно натрапила на нове шоу на MTV (США). Це жахливе шоу, і всі на ньому смітні, але "гра" досить цікава. З Вікіпедії:

Ти один? слід 20 людей, які живуть разом на Гаваях, щоб знайти свою ідеальну відповідність. Якщо 10 чоловіків та 10 жінок зможуть правильно вибрати всі десять ідеальних матчів за десять тижнів, вони отримають 1 мільйон доларів, щоб поділитися між ними.

Тепер для ігрової частини (також з Вікіпедії):

У кожному епізоді акторський склад поєднується з тим, хто вважає, що їх ідеальний матч - змагатися у виклику. Переможці виклику вирушать на побачення та матимуть шанс випробувати свій матч у кабінці правди. Учасники акторського складу виберуть одну з пар-переможців, щоб перейти до кабінки істини, щоб визначити, чи ідеально вони відповідають чи ні. Це єдиний спосіб підтвердити відповідність. Кожен епізод закінчується церемонією співпадіння, де парам буде сказано, скільки ідеальних матчів у них є, але не, які поєдинки є правильними.

TL; DR: Це похідне Mastermind (M (10,10) для конкретності). Правила гри такі:

  1. Ви починаєте з 2-х наборів з 10, назвемо їх множиною A: {A, B, C, D, E, F, G, H, I, J} і задаємо 2: {1,2,3,4,5, 6,7,8,9,10}

  2. Комп'ютер створює рішення (не видно для вас) у вигляді {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10}, де члени в наборі A відображаються на 1-у-1 встановити 2. Іншим прикладом рішення може бути {A2, B5, C10, D8, E1, F7, G6, H4, I9, J3}.

  3. Перед першим кроком ви маєте запитати, чи правильна окрема пара на ваш вибір. Ваше запитання було б у формі {A1} (наприклад, {C8}), і ви отримаєте або 1 (значить правильно), або 0 (значить, ваша здогадка неправильна).

  4. Ваша перша фактична черга. Ви робите свою першу здогадку у вигляді {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10} або будь-якої перестановки на ваш вибір. Ваша здогадка не може містити кратних елементів будь-якого елемента, тобто здогадка {A1, A2, A3, A4, A5, B6, B7, B8, B9, B10} НЕ ПРАВИЛЬНА здогадка. Після кожного повороту комп'ютер повідомляє вам кількість правильних збігів, але НЕ, які збіги є правильними.

  5. Повторюйте кроки 3 та 4, поки ви не отримаєте правильну відповідність кожного матчу (тобто відповідь 10) або до того моменту, поки ваші 10 кроків не збільшаться (що швидше відбудеться). Якщо ви вирішите це до або на 10-му кроці, ви виграєте 1 мільйон доларів. Інакше ти програєш, а деякі люди (або літери та цифри) їдуть додому наодинці, щоб провести вічність зі своїми 10 котами.

Це НЕ найкоротший конкурс коду. Переможець стане тим, хто зможе вирішити випадкову відповідність у найменшій середній кількості здогадок. Розумна швидкість гри та обчислення гри також буде ймовірною. Я припускаю, що середня кількість оборотів майже напевно перевищить 10, тому шанси на те, що ви виграєте приз на 1 мільйон доларів (імовірно, виплачує MTV, а не я), невеликі. Тільки як неможливо це для кидка , щоб виграти головний приз?

Примітка: Розміщувати його у форматі {A1, B2, ...} не обов'язково. Я просто використав цю форму у питанні, щоб абсолютно зрозуміти, що таке головоломка. Якщо ви не ставите його у цій формі, будь ласка, поясніть, як це зателефонувати.

Удачі!


3
Якщо ви хочете, щоб перемогла людина, яка зможе вирішити це в найменш середніх здогадах, чому б не зробити це критеріями виграшу? Я не бачу жодної причини, що це повинно бути змаганням на популярність, коли ідеально вигідна умова виграшу дивиться нам в обличчя.
Геобіць

Наскільки я можу сказати, це питання не має нічого спільного з оптимально граючим Mastermind. Він запитує гру, яка дозволяє користувачеві грати в неї.
feersum

1
Тоді поп-конкурс не є тегом для цього.

1
@ hosch250 Оновлений критерій переможця
dberm22

2
7 оновлень, 2 вибраних, і жодної відповіді. Я знав, що це було важко!
dberm22

Відповіді:


6

Python 2 (запустити швидше, якщо запустити за допомогою Pypy)

Вважається, що майже завжди здогадуються про правильне сполучення в 10 раундів або нижче

Мій алгоритм взято з моєї відповіді на натхнення як моє хобі (див. В Ideone ). Ідея полягає у тому, щоб знайти здогад, який мінімізує кількість можливостей, залишених у гіршому випадку. Мій алгоритм нижче просто змушує його, але щоб заощадити час, він просто вибирає випадкові здогадки, якщо кількість залишених можливостей більше, ніж RANDOM_THRESHOLD. Ви можете пограти з цим параметром, щоб прискорити роботу або побачити кращу ефективність.

Алгоритм досить повільний, в середньому 10 секунд за один пробіг, якщо запускати за допомогою Pypy (якщо використовується звичайний інтерпретатор CPython - це близько 30-х років), тому я не можу перевірити його на всіх перестановках. Але продуктивність досить хороша, після приблизно 30 тестів я не бачив жодного випадку, де б не вдалося знайти правильне сполучення в 10 раундів або нижче.

У будь-якому випадку, якщо це використовується в реальному шоу, у нього є достатньо часу до наступного раунду (тиждень?), Тому цей алгоритм можна використовувати в реальному житті = D

Тому я вважаю, що можна припустити, що в середньому це знайде правильні пари у 10 здогадах чи нижче.

Спробуйте самі. Я міг би покращити швидкість у найближчі кілька днів (EDIT: подальше вдосконалення здається складною, тому я просто залишу код таким, який є. Я намагався робити лише випадковий вибір, але навіть у size=7такому випадку він не вдається в 3 з 5040 випадків , тому я вирішив дотримати розумніший метод). Ви можете запустити його як:

pypy are_you_the_one.py 10

Або, якщо ви просто хочете побачити, як це працює, введіть меншу кількість (щоб вона працювала швидше)

Щоб виконати повний тест (попередження: це займе дуже багато часу size> 7), введіть від’ємне число.

Повний тест на size=7(завершено через 2 м 32):

...
(6, 5, 4, 1, 3, 2, 0): 5 здогадів
(6, 5, 4, 2, 0, 1, 3): 5 здогадів
(6, 5, 4, 2, 0, 3, 1): 4 здогадки
(6, 5, 4, 2, 1, 0, 3): 5 здогадів
(6, 5, 4, 2, 1, 3, 0): 6 здогадів
(6, 5, 4, 2, 3, 0, 1): 6 здогадів
(6, 5, 4, 2, 3, 1, 0): 6 здогадів
(6, 5, 4, 3, 0, 1, 2): 6 здогадів
(6, 5, 4, 3, 0, 2, 1): 3 здогадки
(6, 5, 4, 3, 1, 0, 2): 7 здогадів
(6, 5, 4, 3, 1, 2, 0): 7 здогадів
(6, 5, 4, 3, 2, 0, 1): 4 здогадки
(6, 5, 4, 3, 2, 1, 0): 7 здогадів
Середня кількість: 5,05
Максимальна кількість: 7
Мінімальна кількість: 1
Кількість успіхів: 5040

Якщо RANDOM_THRESHOLDі CLEVER_THRESHOLDобидва встановлені на дуже високе значення (наприклад, 50000), це змусить алгоритм знайти оптимальну здогадку, яка мінімізує кількість можливостей у гіршому випадку. Це дуже повільно, але дуже потужно. Наприклад, запускаючи його, size=6стверджує, що він може знайти правильні пари у максимум 5 раундів.

Хоча середнє значення вище, ніж використання наближення (яке в середньому становить 4,11 раунда), але це завжди вдається, навіть більше, якщо залишиться один раунд. Це ще більше посилює нашу гіпотезу про те size=10, що коли , майже завжди слід знаходити правильні пари в 10 раундів або менше.

Результат (завершено за 3м 9с):

(5, 4, 2, 1, 0, 3): 5 здогадів
(5, 4, 2, 1, 3, 0): 5 здогадів
(5, 4, 2, 3, 0, 1): 4 здогадки
(5, 4, 2, 3, 1, 0): 4 здогадки
(5, 4, 3, 0, 1, 2): 5 здогадів
(5, 4, 3, 0, 2, 1): 5 здогадів
(5, 4, 3, 1, 0, 2): 5 здогадів
(5, 4, 3, 1, 2, 0): 5 здогадів
(5, 4, 3, 2, 0, 1): 5 здогадів
(5, 4, 3, 2, 1, 0): 5 здогадів
Середня кількість: 4,41
Максимальна кількість: 5
Мінімальна кількість: 1
Число успіхів: 720

Кодекс.

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)

Це справді неймовірно. Я пробігав його ще 100 разів, і ще потрібно взяти більше 10 здогадок, щоб знайти рішення. Я бачив пару 10, і навіть пару 6. (Ви кажете, що не бачили жодного випадку, коли він не може знайти правильне сполучення під 10 раундів. Це, мабуть, повинно сказати "за 10 чи менше раундів", але це просто семантика.) Це фантастично! Я припускаю, що ваше лямбда-значення - це якесь вимірювання ентропії, яке дозволяє зробити оптимальну здогадку, але я не бачу, як і де це встановлено. Це я просто щільна, а не звинувачення у вашій програмі. Неймовірна робота!
dberm22

Намагається мінімізувати кількість можливостей, залишених у гіршому випадку ( len(remove_perms ...)частина). І так, я мав на увазі в <10 раундів =). Btw в коді вище дійсно оптимальної здогадки ніколи не робиться, оскільки я поставив CLEVER_THRESHOLD=0, це означає, що я намагаюся лише здогадуватися зі списку можливостей, хоча оптимальна здогадка може бути поза цим набором. Але я вирішив відключити це, щоб заощадити час. Я додав повний тест на size=7, показуючи, що це завжди вдається.
justhalf

1
Я запускаю ваш код протягом ночі з "Розумним порогом = 0" (починаючи з (9,8,7,6,5,4,3,2,1,0) і працюю назад через усі перестановки). Мені поки що лише 2050 рік, але було 13 випадків, коли це пройшло 11 витків. Вибірка зразка - (9, 8, 7, 4, 0, 6, 3, 2, 1, 5): 9 здогадок, Середня кількість: 8,29, Максимальна кількість: 11, Кількість хвилин: 4, Кількість успіхів: 2037, Число Оцінено: 2050. Якби "Розумний поріг" був встановлений правильно, я думаю, що ці 11-і стали б 10-ма. І все-таки в середньому 8,3 досить чудові.
dberm22

@ dberm22: Так, дякую за те, що запустив цей повільний алгоритм протягом ночі! Я пройшов повний тест size=8і виявив, що остання версія має лише 40308 успіхів (замість 40320), якщо використовується ця настройка. Але це все ще 99,97% успішності! Здогадайтесь, ми можемо змусити організатора телешоу збанкрутувати.
justhalf

3

CJam -19 оборотів - Стратегія ідіотів

Це не серйозна відповідь, а демонстрація. Це ідіотське рішення, коли він не враховує кількість правильних відомостей про пару, наданих з другої частини ходу. Для повністю випадкових пар це в середньому займає 27 тижнів. Ця відповідь є недостатньо чіткою, як я вже сказав, але вказує на те, що шанси на інтелектуальну групу (набагато розумніші, ніж ця програма), ймовірно, не такі тонкі, як ви могли б очікувати. Більш інтелектуальні алгоритми, які я написав, однак запускають багато більше часу, щоб я міг фактично отримати відповіді від них.

Оновлення: Код нижче оновлено, щоб використовувати стан, що він повинен видаляти ті, які не працюють, якщо єдиними правильними є ті, про які ми вже знали, що вони є правильними. Він також був відредагований, щоб показати мій генератор випадкових "правильних відповідей". Середній результат зараз лише 19. Це все ще німе рішення, але він є кращим, ніж попередній.

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

Також зверніть увагу: неохайне поводження з помилками полягає в тому, що програмування простіше, ніж тоді більш розумний метод.
kaine

+1 за те, що насправді досить сміливий, щоб реалізувати рішення. Я насправді дуже вражений, що для здогадки про правильне рішення потрібно в середньому лише 27 витків. Я б припустив, що, як ви здогадуєтесь, наступні поєдинки знайти в експоненціальному (ну, фактично) простіше. Спасибі! Мені було б цікаво дізнатися, чи може хтось отримати менше 10. Ти дав мені надію!
dberm22

Якщо 27 дивно, подивіться на це! Я жартую вбік, я думаю, що рішення, яке гарантує 10 або принаймні отримує його в середньому, можливо. Я просто не можу змусити такого алгоритму працювати у розумні часові рамки.
kaine

19 ... Я ще більше вражений. Хоча лише запитання ... у вашій програмі, де ви говорите "Здогадується номер для першого невідомого. Якщо справа встановлює пару; в іншому випадку стирає її". Якщо це неправильно ... вам слід додати його до списку збігів, які ви знаєте, невірні, тому ви не вгадаєте його знову (або в перестановці, або як окрема здогадка).
dberm22

Це означає стерти його зі списку можливостей; У мене є список можливих, а не список неможливих. О, і я забув зазначити, що самець має займати позицію в масиві, а самці - цифри 0-9 (або навпаки). Я б використовував формат A5 B2 і т.д., якби це було більш серйозним поданням.
kaine

3

Швидка багатопотокова версія C ++

Я знаю, що минув час, коли ця тема була активною, але я маю круте доповнення: - Ось C ++ реалізація алгоритму minimax для Are You One? , яка використовує багатопотоковість для прискорення оцінки кожної можливої ​​здогадки.

Ця версія набагато швидша, ніж версія Python (на 100 разів швидша, коли для початкової версії Python встановлено максимум RANDOM_THRESHOLDта CLEVER_THRESHOLD). Він не використовує жодних випадкових здогадок, а, швидше, оцінює всі перестановки і подає як його здогадку перестановку, яка виключає найбільшу кількість можливих рішень (з огляду на найгірший варіант відповіді).

Для менших ігор, виклик "ayto -n" запустить гру на всіх n! можливі приховані відповідники, і ви дасте короткий підсумок результатів наприкінці.

Оскільки оцінити всі 10! можливі приховані відповідники, якщо ви називаєте "айто 10", наприклад, тренажер робить свої фіксовані спочатку три здогадки, потім запускає мінімакс, щоб вибрати свої здогадки, і передбачає, що йому було надано найгірший випадок. Це призводить нас вниз по "гіршому випадку" до прихованого вектора, який, імовірно, в класі векторів, який використовує алгоритм максимальної кількості здогадок для ідентифікації. Ця гіпотеза не перевірена.

До n = 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 << ")";
    }
}

Я перемістив це до "Ти один"? на GitHub з оновленим, швидшим кодом.
Chris Chute
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.