あなたがそうですか?(首謀者の誘導体)


15

私はあなたのために厳しいものを持っています!

私のガールフレンドは最近、MTV(米国)で新しいショーに出会いました。それはひどいショーであり、その上で誰もがくだらないですが、「ゲーム」はかなり興味深いです。ウィキペディアから:

あなたがそうですか?ハワイで一緒に生活している20人の人々をフォローして、ぴったりの相手を見つけます。10人の男性と10人の女性が10週間で10個の完全一致をすべて正しく選択できれば、100万ドルの利益を得ることができます。

ゲーム部分について(Wikipediaからも):

キャストの各エピソードは、彼らの完璧な試合がチャレンジで競うことであると彼らが信じる人とペアになります。チャレンジの勝者はデートに行き、真実のブースで試合をテストする機会があります。キャストメンバーは、勝者のカップルのいずれかを選択して真実のブースに行き、完全一致かどうかを判断します。 これが一致を確認する唯一の方法です。各エピソードは、カップルに完全一致がいくつあるかを知らせるマッチング式で終了しますが、どのマッチが正しいかではありません。

TL; DR: これは首謀者の派生物です(具体的にはM(10,10))。ゲームのルールは次のとおりです。

  1. 10個の2セットから始めます。それらをセット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にマッピングされます解決策の別の例は{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ターン前または10ターン目に解決した場合、100万ドルを獲得します。そうでなければ、あなたは負け、何人かの人々(または文字と数字)は一人で家に帰り、10匹の猫と永遠に過ごす。

これは最短のコードコンテストではありません。推測の最小平均数でランダムマッチングを解決できる人が勝者になります。巧妙なゲームプレイと計算速度も考慮されます。平均ターン数はほぼ確実に10を超えると想定しているため、$ 100万の賞金(おそらく私ではなくMTVから支払われる)を獲得する確率はわずかです。ただ、どのようにそれが大賞を獲得するキャストは不可能でしょうか?

注:{A1、B2、...}形式で入力する必要はありません。質問でそのフォームを使用して、パズルが何であるかを完全に明確にしました。このフォームに入れない場合は、呼び出し方法を説明してください。

幸運を!


3
あなたが勝つために少なくとも平均の推測では、それを解決することができる人が欲しいなら、なぜしないことを受賞基準?完全に有効な勝利条件が私たちを正面から見つめているとき、これが人気コンテストになるはずの理由がわかりません。
Geobits 14年

私の知る限り、この質問は、Mastermindの最適なプレイとは何の関係もありません。ユーザーがプレイできるゲームを要求します。
feersum 14年

1
その場合、pop-contestはこのタグではありません。
Hosch250 14年

1
勝者のためのhosch250更新基準@
dberm22

2
7つのアップ投票、2つのお気に入り、および回答なし。これは難しいことだと思いました!
dberm22

回答:


6

Python 2(Pypyを使用して実行すると高速に実行されます)

10ラウンド以下でほぼ常に正しいペアリングを推測すると信じられていた

私のアルゴリズムは、私の趣味としての首謀者の答えから取られています(Ideoneを参照)。考えは、最悪の場合に残される可能性の数を最小にする推測を見つけることです。以下の私のアルゴリズムは総当たり攻撃ですが、時間を節約するために、残っている可能性の数がRANDOM_THRESHOLDます。このパラメーターをいじって、速度を上げたり、パフォーマンスを改善したりできます。

アルゴリズムは非常に遅く、Pypyを使用して実行すると1回の実行で平均10秒(通常のCPythonインタープリターを使用する場合は約30秒)なので、順列全体でテストすることはできません。しかし、パフォーマンスは非常に良好です。約30回のテストの後、10ラウンド以下で正しいペアリングを見つけることができないインスタンスは見られませんでした。

とにかく、これが実生活のショーで使用される場合、次のラウンド(1週間?)までに十分な時間があるため、このアルゴリズムは実生活で使用できます= D

したがって、平均で10回以下の推測で正しいペアリングが見つかると想定するのは安全だと思います。

自分で試してみてください。、さらに向上させることが難しいようであるように私は、コードを残しておきます、私は唯一のランダムピックをやってみましたが、それでも時:私は(次の数日でEDITを速度を向上させる可能性がある。size=7それは5040例3で失敗し、 、私は賢い方法を維持することにしました)。次のように実行できます。

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_THRESHOLDCLEVER_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ラウンドで=)を意味しました。上記のコードでは、実際に最適な推測が行われることはありません。私がを入れたのはCLEVER_THRESHOLD=0、可能性のリストから推測しようとするだけだからです。しかし、時間を節約するためにそれを無効にすることにしました。の完全なテストを追加しsize=7、常に成功することを示しました。
ちょうど半分14年

1
「賢いしきい値= 0」でコードを一晩実行しました((9,8,7,6,5,4,3,2,1,0)から始まり、すべての順列を逆方向に処理します)。私はこれまで2050年に過ぎませんが、11ターンかかった13件のケースがあります。サンプル印刷-(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しましたが、この設定を使用した場合、最新バージョンの成功は(40320ではなく)40308のみであることがわかりました。しかし、それでも99.97%の成功率です!テレビ番組のオーガナイザーを倒産させることができると思います。
14年

3

CJam -19ターン-白痴の戦略

これは深刻な答えではなく、デモンストレーションです。これは馬鹿の解決策であり、彼はターンの2番目の部分から提供される正しいペアリング情報の数を考慮しません。完全にランダムなペアリングでは、これには平均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";

また注意してください:ずさんなエラー処理は、よりインテリジェントなメソッドよりもプログラミングが簡単だからです。
ケイン14年

実際にソリューションを実装するのに十分な勇気があるため、+ 1。正しい解決策を推測するのに平均で27ターンしかかからないことに実際にショックを受けています。あなたが正しく推測すると、後続の一致は指数関数的に(まあ、階乗的に)見つけやすくなると思います。ありがとう!誰かが10未満になる可能性があるかどうかを確認したいと思います。
dberm22

27が驚くならそれを見てください!冗談はともかく、私は10を保証するか、少なくとも平均でそれを得る解決策が可能であると思います。そのようなアルゴリズムを妥当な時間枠で動作させることはできません。
ケイン14年

19 ...私はさらにショックを受けました。ただ質問です...あなたのプログラムでは、「最初の未知の数字を推測します。ペアを正しく設定すれば消去します」と言います。間違っている場合は、正しくないことがわかっている一致のリストに追加する必要がありますので、再度推測する必要はありません(順列または別の推測として)。
dberm22 14年

それは可能性のリストからそれを消去することを意味します。不可能なもののリストではなく、可能なもののリストを持っています。ああ、私はこれが男性が配列の位置にあり、女性が数字の0-9である(またはその逆)ことを言及するのを忘れました。より深刻な提出であれば、A5 B2などの形式を使用します。
ケイン14年

3

高速マルチスレッドC ++バージョン

このスレッドがアクティブになってからしばらく経ちましたが、共有するクールな追加があります。これは、Are You The OneのミニマックスアルゴリズムのC ++実装です。、マルチスレッドを使用して、考えられる各推測の評価を高速化します。

このバージョンは、Pythonバージョンよりもはるかに高速です(元のPythonバージョンがmaximum RANDOM_THRESHOLDおよびに設定されている場合、100倍以上高速ですCLEVER_THRESHOLD)。ランダムな推測を使用せず、すべての順列を評価し、可能な限り多くの解決策を排除する順列を推測として送信します(最悪の場合の応答を想定)。

小さなゲームの場合、「ayto -n」を呼び出すと、すべてのnでゲームが実行されます。隠された一致の可能性があり、最後に結果の短い要約が表示されます。

10個すべてを評価するのはまだ難しいので!たとえば、「ayto 10」を呼び出す場合、シミュレータは最初の3つの固定推測を行い、minimaxを実行して推測を選択し、最悪の場合の評価が与えられたと想定します。これにより、「最悪の場合のパス」から、識別のために最大数の推測をアルゴリズムに使用するベクトルのクラスにあると思われる隠しベクトルに導かれます。この推測はテストされていません。

までの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 << ")";
    }
}

これをAre You The Oneに移動しましたか?GitHubで、より高速なコードを更新しました。
クリスシュート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.