당신은 하나입니까? (마스터 마인드 파생)


15

나는 당신을 위해 힘든 것을 가지고 있습니다!

내 여자 친구는 최근 MTV (미국)에서 새로운 쇼를 보았습니다. 그것은 끔찍한 쇼이며, 그것에있는 모든 사람들은 쓰레기이지만 "게임"은 꽤 흥미 롭습니다. Wikipedia에서 :

당신은 하나입니까? 하와이에서 함께 살고있는 20 명의 사람들을 따라 완벽하게 어울립니다. 10 명의 남성과 10 명의 여성이 10 주 만에 10 개의 완벽한 경기를 모두 올바르게 선택할 수 있다면 백만 달러를 벌어 들일 것입니다.

이제 게임 부분 (Wikipedia에서도) :

각 에피소드는 캐스트가 자신의 완벽한 경기가 도전에 참여한다고 믿는 사람과 짝을 이룹니다. 도전의 승자는 날짜에 가서 진실 부스에서 자신의 경기를 테스트 할 수있는 기회를 갖게됩니다. 출연진은 승리 커플 중 하나를 선택하여 진실 부스로 가서 그들이 완벽한 경기인지 아닌지를 결정합니다. 이것이 일치를 확인하는 유일한 방법입니다. 각 에피소드는 짝짓기 행사로 끝납니다. 여기서 커플은 몇 개의 완벽한 경기를하는지 알려 주지만 어떤 경기가 맞지 않습니까?

TL; DR : 이것은 Mastermind 파생물입니다 (구체적으로 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의 구성원은 일대일로 맵핑됩니다. 솔루션의 다른 예는 {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. 모든 경기가 올바르게 될 때까지 (예 : 10의 응답) 또는 10 개의 움직임이 올 때까지 (둘 중 빠른 시간) 3 단계와 4 단계를 반복하십시오. 만약 당신이 10 턴 전에 또는 10 턴에 그것을 해결한다면, 당신은 백만 달러를 이깁니다. 그렇지 않으면 길을 잃고 일부 사람들 (또는 글자와 숫자)은 혼자 집에 가서 10 마리의 고양이와 함께 영원을 보냅니다.

이것은 가장 짧은 코드 콘테스트가 아닙니다. 최소한의 임의의 일치를 해결할 수있는 사람 평균 추측의 수는 승자가 될 것입니다. 영리한 게임 플레이와 계산 속도도 고려할 것입니다. 평균 턴 수는 거의 10보다 클 것이라고 가정하고 있습니다. 따라서 100 만 달러의 상금을 수상 할 가능성은 매우 적습니다. 그냥 방법 은 대상을 이길 수있는 캐스트 불가능하다?

참고 : 반드시 {A1, B2, ...} 형식으로 입력 할 필요는 없습니다. 나는 질문에서 그 형태를 사용하여 퍼즐이 무엇인지 완전히 분명히했습니다. 이 양식을 작성하지 않은 경우 전화 방법을 설명하십시오.

행운을 빕니다!


3
당신이 이길하기 위해 최소 평균 추측에 그것을 해결할 수있는 사람을 원하는 경우에, 왜하지 것이 승리 조건? 완벽하게 유효한 승리 조건이 우리를 쳐다보고있을 때 이것이 인기 콘테스트가되어야하는 이유를 알 수 없습니다.
Geobits

내가 알 수있는 한, 질문은 Mastermind를 최적으로 재생하는 것과 관련이 없습니다. 사용자가 게임을 할 수있는 게임을 요청합니다.
feersum

1
그런 다음 pop-contest는 이에 대한 태그가 아닙니다.

1
우승자 @ hosch250 업데이트 기준
dberm22

2
업 보트 7 개, 즐겨 찾기 2 개, 답변 없음. 나는 이것이 힘든 것을 알았습니다!
dberm22

답변:


6

Python 2 (Pypy를 사용하여 실행하면 더 빠르게 실행)

거의 항상 10 라운드 이하에서 올바른 페어링을 추측 할 수 있다고 생각

내 알고리즘은 내 취미로 마스터 마인드에 대한 답변에서 가져옵니다 ( Ideone 참조 ). 아이디어는 최악의 경우 남은 가능성을 최소화하는 추측을 찾는 것입니다. 아래의 알고리즘은 무차별 강제하지만 시간을 절약하기 위해 남은 가능성의 수가보다 큰 경우 무작위 추측을 선택 RANDOM_THRESHOLD합니다. 이 매개 변수를 사용하여 작업 속도를 높이거나 성능을 향상시킬 수 있습니다.

Pypy (일반 CPython 인터프리터를 사용하는 경우 약 30 초)를 사용하여 실행하면 알고리즘이 한 번 실행하는 데 평균 10 초가 걸리므로 전체 순열에서 테스트 할 수 없습니다. 그러나 성능은 꽤 좋습니다. 약 30 번의 테스트 후에 10 라운드 이하에서 올바른 페어링을 찾을 수없는 인스턴스를 보지 못했습니다.

어쨌든, 이것이 실제 쇼에서 사용된다면, 다음 라운드 (1 주일) 전에 많은 시간이 있기 때문에이 알고리즘은 실제 = D에서 사용될 수 있습니다.

따라서 평균적으로 10 추측 이하에서 올바른 페어링을 찾을 것이라고 가정하는 것이 안전하다고 생각합니다.

직접 해보십시오. 나는 며칠 안에 속도를 향상시킬 수 있습니다 (편집 : 더 향상시키기가 어려워 보입니다. 따라서 코드를 그대로 두겠습니다. 임의의 픽만 시도했지만 50 size=7에서 3 중 실패했습니다. 그래서 나는 영리한 방법을 유지하기로 결정했습니다). 다음과 같이 실행할 수 있습니다.

pypy are_you_the_one.py 10

또는 작동 방식을 보려면 더 적은 수를 입력하십시오 (더 빠르게 실행되도록).

전체 테스트를 실행하려면 (경고 : size> 7보다 시간이 오래 걸림) 음수를 입력하십시오.

전체 테스트 size=7(2m 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
Num 성공 : 5040

경우 RANDOM_THRESHOLDCLEVER_THRESHOLD(50000처럼) 매우 높은 값으로 설정되어, 그것은 최적의 추측을 찾는 알고리즘을 강제 것이다 최소화하는 최악의 경우 가능성의 수. 이것은 매우 느리지 만 매우 강력합니다. 예를 들어,이를 실행하면 size=6최대 5 라운드에서 올바른 페어링을 찾을 수 있습니다.

평균은 근사값 (평균 4.11 라운드)을 사용하는 것보다 높지만 항상 1 라운드를 남겨두면 항상 성공합니다. 이것은 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
'Clever Threshold = 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, Num 성공 : 2037, Num 'Clever Threshold'가 제대로 설정 되었다면, 11은 10이 될 것이라고 확신합니다. 여전히 평균 8.3은 꽤 훌륭합니다.
dberm22

@ dberm22 : 예,이 느린 알고리즘을 밤새 실행 해 주셔서 감사합니다! size=8이 설정을 사용하면 전체 테스트를 실행 하고 최신 버전의 경우 40320 대신 40308의 성공 만 발견했습니다. 그러나 여전히 99.97 %의 성공률입니다! TV 쇼 주최자가 파산하게 만들 수있을 것 같아요.
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 ++ 버전

이 스레드가 활성화 된 이후 오랜 시간이 걸렸지 만 공유 할 멋진 추가 기능이 있습니다. 다음 은 Are You The One 의 minimax 알고리즘에 대한 C ++ 구현입니다 . 멀티 스레딩을 사용하여 가능한 각 추측의 평가 속도를 높입니다.

이 버전은 Python 버전보다 훨씬 빠릅니다 (원래 Python 버전이 최대 RANDOM_THRESHOLD및 로 설정된 경우 100 배 이상 빠름 CLEVER_THRESHOLD). 임의의 추측을 사용하지 않고 모든 순열을 평가하고 가능한 가장 많은 수의 솔루션을 제거하는 순열을 추측으로 제출합니다 (최악의 경우 응답).

작은 게임의 경우 "ayto -n"을 호출하면 모든 n에서 게임이 실행됩니다! 숨겨진 일치 항목이 있으며 마지막에 결과를 간략하게 요약합니다.

10 개 모두를 평가하는 것은 여전히 ​​어려운 일입니다! 예를 들어, "ayto 10"을 호출하면 가능한 숨겨진 일치 항목이 시뮬레이터에서 처음 세 가지 추측을 한 다음 minimax를 실행하여 추측을 선택하고 최악의 평가를 받았다고 가정합니다. 이것은 알고리즘을 식별하기 위해 최대 추측 수를 취하는 벡터 클래스에있는 숨겨진 벡터에 대한 "최악의 경로"로 안내합니다. 이 추측은 테스트되지 않았습니다.

최대 9 명 이상 촬영하고있다 시뮬레이션이되지 않았습니다 N을 해결하기집니다.

이것을 직접 테스트하기 위해 컴파일 예는 다음과 같습니다.

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

다음은 출력이 포함 된 작은 예입니다.

$ ./ayto -4
Found (0, 1, 2, 3) in 2 guesses.
Found (0, 1, 3, 2) in 3 guesses.
Found (0, 2, 1, 3) in 2 guesses.
Found (0, 2, 3, 1) in 3 guesses.
Found (0, 3, 1, 2) in 2 guesses.
Found (0, 3, 2, 1) in 2 guesses.
Found (1, 0, 2, 3) in 1 guesses.
Found (1, 0, 3, 2) in 3 guesses.
Found (1, 2, 0, 3) in 3 guesses.
Found (1, 2, 3, 0) in 3 guesses.
Found (1, 3, 0, 2) in 3 guesses.
Found (1, 3, 2, 0) in 3 guesses.
Found (2, 0, 1, 3) in 3 guesses.
Found (2, 0, 3, 1) in 3 guesses.
Found (2, 1, 0, 3) in 3 guesses.
Found (2, 1, 3, 0) in 3 guesses.
Found (2, 3, 0, 1) in 3 guesses.
Found (2, 3, 1, 0) in 3 guesses.
Found (3, 0, 1, 2) in 3 guesses.
Found (3, 0, 2, 1) in 3 guesses.
Found (3, 1, 0, 2) in 3 guesses.
Found (3, 1, 2, 0) in 3 guesses.
Found (3, 2, 0, 1) in 3 guesses.
Found (3, 2, 1, 0) in 3 guesses.
***** SUMMARY *****
Avg. Turns: 2.75
Worst Hidden Vector: (0, 1, 3, 2) in 3 turns.

암호

/* Multithreaded Mini-max Solver for MTV's Are You The One? */

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <algorithm>
#include <numeric>
#include <string>
#include <vector>
#include <map>
#include <thread>
#include <cmath>

#define TEN_FACT (3628800)
#define NUM_CHUNKS (8)

using std::cout;
using std::cin;
using std::endl;
using std::vector;
using std::string;
using std::map;
using std::pair;
using std::find;
using std::abs;
using std::atoi;
using std::next_permutation;
using std::max_element;
using std::accumulate;
using std::reverse;
using std::thread;

struct args {
    vector<string> *perms;
    vector<string> *chunk;
    pair<string, int> *cd;
    int thread_id;
};

void simulate_game(const string &hidden, map<string, int> &turns_taken,
                   bool running_all);
bool picmp(const pair<string, int> &p1, const pair<string, int> &p2);
double map_avg(const map<string, int> &mp);
int nrand(int n);
int evaluate(const string &sol, const string &query);
vector<string> remove_perms(vector<string> &perms, int eval, string &query);
pair<string, int> guess_tb(vector<string> &perms, vector<string> &guessed_tb, int turn);
pair<string, int> guess_pm(vector<string> &perms, vector<string> &guessed, int turn);
void make_chunks(vector<string> &orig, vector<vector<string> > &chunks, int n);
string min_candidate(pair<string, int> *candidates, int n);
void get_score(struct args *args);
int wc_response(string &guess, vector<string> &perms);
bool prcmp(pair<int, int> x, pair<int, int> y);
void sequence_print(string s);
struct args **create_args(vector<string> &perms, pair<string, int> *cd, vector<string> &chunk, int thread_id);


vector<string> ORIGPERMS;

int main(int argc, char **argv)
{
    int sz;
    map<string, int> turns_taken;
    const string digits = "0123456789";
    bool running_all = false;

    if (argc != 2) {
        cout << "usage: 'ayto npairs'" << endl;
        return 1;
    } else {
        if ((sz = atoi(argv[1])) < 0) {
            sz = -sz;
            running_all = true;
        }
        if (sz < 3 || sz > 10) {
            cout << "usage: 'ayto npairs' where 3 <= npairs <= 10" << endl;;
            return 1;
        }
    }

    // initialize ORIGPERMS and possible_perms
    string range = digits.substr(0, sz);
    do {
        ORIGPERMS.push_back(range);
    } while (next_permutation(range.begin(), range.end()));

    if (running_all) {
        for (vector<string>::const_iterator it = ORIGPERMS.begin();
             it != ORIGPERMS.end(); ++it) {
            simulate_game(*it, turns_taken, running_all);
        }
        cout << "***** SUMMARY *****\n";
        cout << "Avg. Turns: " << map_avg(turns_taken) << endl;
        pair<string, int> wc = *max_element(turns_taken.begin(),
                                            turns_taken.end(), picmp);
        cout << "Worst Hidden Vector: ";
        sequence_print(wc.first);
        cout << " in " << wc.second << " turns." << endl;
    } else {
        string hidden = ORIGPERMS[nrand(ORIGPERMS.size())];
        simulate_game(hidden, turns_taken, running_all);
    }

    return 0;
}

// simulate_game:  run a single round of AYTO on hidden vector
void simulate_game(const string &hidden, map<string, int> &turns_taken,
                   bool running_all)
{
    vector<string> possible_perms = ORIGPERMS;
    pair<string, int> tbguess;
    pair<string, int> pmguess;
    vector<string> guessed;
    vector<string> guessed_tb;
    int e;
    int sz = hidden.size();

    if (!running_all) {
        cout << "Running AYTO Simulator on Hidden Vector: ";
        sequence_print(hidden);
        cout << endl;
    }

    for (int turn = 1; ; ++turn) {
        // stage one: truth booth
        if (!running_all) {
            cout << "**** Round " << turn << "A ****" << endl;
            cout << "Num. Possibilities: " << possible_perms.size() << endl;
        }
        tbguess = guess_tb(possible_perms, guessed_tb, turn);
        if (!running_all) {
            cout << "Guess: ";
            sequence_print(tbguess.first);
            cout << endl;
            e = tbguess.second;
            cout << "Worst-Case Evaluation: " << e << endl;
        } else {
            e = evaluate(hidden, tbguess.first);
        }
        possible_perms = remove_perms(possible_perms, e, tbguess.first);

        // stage two: perfect matching
        if (!running_all) {
            cout << "Round " << turn << "B" << endl;
            cout << "Num. Possibilities: " << possible_perms.size() << endl;
        }
        pmguess = guess_pm(possible_perms, guessed, turn);

        if (!running_all) {
            cout << "Guess: ";
            sequence_print(pmguess.first);
            cout << endl;
            e = pmguess.second;
            cout << "Worst-Case Evaluation: " << e << endl;
        } else {
            e = evaluate(hidden, pmguess.first);
        }
        if (e == sz) {
            cout << "Found ";
            sequence_print(pmguess.first);
            cout << " in " << turn << " guesses." << endl;
            turns_taken[pmguess.first] = turn;
            break;
        }

        possible_perms = remove_perms(possible_perms, e, pmguess.first);
    }
}

// map_avg:  returns average int component of a map<string, int> type
double map_avg(const map<string, int> &mp)
{
    double sum = 0.0;

    for (map<string, int>::const_iterator it = mp.begin(); 
         it != mp.end(); ++it) {
        sum += it->second;
    }

    return sum / mp.size();
}

// picmp:  comparison function for pair<string, int> types, via int component
bool picmp(const pair<string, int> &p1, const pair<string, int> &p2)
{
    return p1.second < p2.second;
}

// nrand:  random integer in range [0, n)
int nrand(int n)
{
    srand(time(NULL));

    return rand() % n;
}

// evaluate:  number of black hits from permutation or truth booth query
int evaluate(const string &sol, const string &query)
{
    int hits = 0;

    if (sol.size() == query.size()) {
        // permutation query
        int s = sol.size();
        for (int i = 0; i < s; i++) {
            if (sol[i] == query[i])
                ++hits;
        }
    } else {
        // truth booth query
        if (sol[atoi(query.substr(0, 1).c_str())] == query[1])
            ++hits;
    }

    return hits;
}

// remove_perms:  remove solutions that are no longer possible after an eval
vector<string> remove_perms(vector<string> &perms, int eval, string &query)
{
    vector<string> new_perms;

    for (vector<string>::iterator i = perms.begin(); i != perms.end(); i++) {
        if (evaluate(*i, query) == eval) {
            new_perms.push_back(*i);
        }
    }

    return new_perms;
}

// guess_tb:  guesses best pair (pos, val) to go to the truth booth
pair<string, int> guess_tb(vector<string> &possible_perms,
                           vector<string> &guessed_tb, int turn)
{
    static const string digits = "0123456789";
    int n = possible_perms[0].size();
    pair<string, int> next_guess;

    if (turn == 1) {
        next_guess.first = "00";
        next_guess.second = 0;
    } else if (possible_perms.size() == 1) {
        next_guess.first = "0" + possible_perms[0].substr(0, 1);
        next_guess.second = 1;
    } else {
        map<string, double> pair_to_count;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                pair_to_count[digits.substr(i, 1) + digits.substr(j, 1)] = 0;
            }
        }

        // count up the occurrences of each pair in the possible perms
        for (vector<string>::iterator p = possible_perms.begin();
             p != possible_perms.end(); p++) {
            int len = possible_perms[0].size();
            for (int i = 0; i < len; i++) {
                pair_to_count[digits.substr(i, 1) + (*p).substr(i, 1)] += 1;
            }
        }

        double best_dist = 1;
        int perm_cnt = possible_perms.size();
        for (map<string, double>::iterator i = pair_to_count.begin();
             i != pair_to_count.end(); i++) {
            if (find(guessed_tb.begin(), guessed_tb.end(), i->first)
                == guessed_tb.end()) {
                // hasn't been guessed yet
                if (abs(i->second/perm_cnt - .5) < best_dist) {
                    next_guess.first = i->first;
                    best_dist = abs(i->second/perm_cnt - .5);
                    if (i->second / perm_cnt < 0.5) // occurs in < half perms
                        next_guess.second = 0;
                    else                            // occurs in >= half perms
                        next_guess.second = 1;
                }
            }
        }
    }

    guessed_tb.push_back(next_guess.first);

    return next_guess;
}

// guess_pm:  guess a full permutation using minimax
pair<string, int> guess_pm(vector<string> &possible_perms,
                           vector<string> &guessed, int turn)
{
    static const string digits = "0123456789";
    pair<string, int> next_guess;
    vector<vector<string> > chunks;
    int sz = possible_perms[0].size();

    // on first turn, we guess "0, 1, ..., n-1" if truth booth was correct
    // or "1, 0, ..., n-1" if truth booth was incorrect
    if (turn == 1) {
        int fact, i;
        for (i = 2, fact = 1; i <= sz; fact *= i++)
            ;
        if (possible_perms.size() == fact) {
            next_guess.first = digits.substr(0, sz);
            next_guess.second = 1;
        } else {
            next_guess.first = "10" + digits.substr(2, sz - 2);
            next_guess.second = 1;
        }
    } else if (possible_perms.size() == 1) {
        next_guess.first = possible_perms[0];
        next_guess.second = possible_perms[0].size();
    } else {
        // run multi-threaded minimax to get next guess
        pair<string, int> candidates[NUM_CHUNKS];
        vector<thread> jobs;
        make_chunks(ORIGPERMS, chunks, NUM_CHUNKS);
        struct args **args = create_args(possible_perms, candidates, chunks[0], 0);

        for (int j = 0; j < NUM_CHUNKS; j++) {
            args[j]->chunk = &(chunks[j]);
            args[j]->thread_id = j;
            jobs.push_back(thread(get_score, args[j]));
        }
        for (int j = 0; j < NUM_CHUNKS; j++) {
            jobs[j].join();
        }

        next_guess.first = min_candidate(candidates, NUM_CHUNKS);
        next_guess.second = wc_response(next_guess.first, possible_perms);

        for (int j = 0; j < NUM_CHUNKS; j++)
            free(args[j]);
        free(args);
    }

    guessed.push_back(next_guess.first);

    return next_guess;
}

struct args **create_args(vector<string> &perms, pair<string, int> *cd, vector<string> &chunk, int thread_id)
{
    struct args **args = (struct args **) malloc(sizeof(struct args*)*NUM_CHUNKS);
    assert(args);
    for (int i = 0; i < NUM_CHUNKS; i++) {
        args[i] = (struct args *) malloc(sizeof(struct args));
        assert(args[i]);
        args[i]->perms = &perms;
        args[i]->cd = cd;
    }

    return args;
}

// make_chunks:  return pointers to n (nearly) equally sized vectors
//                from the original vector
void make_chunks(vector<string> &orig, vector<vector<string> > &chunks, int n)
{
    int sz = orig.size();
    int chunk_sz = sz / n;
    int n_with_extra = sz % n;
    vector<string>::iterator b = orig.begin();
    vector<string>::iterator e;

    for (int i = 0; i < n; i++) {
        int m = chunk_sz;    // size of this chunk
        if (n_with_extra) {
            ++m;
            --n_with_extra;
        }
        e = b + m;
        vector<string> subvec(b, e);
        chunks.push_back(subvec);
        b = e;
    }
}

// min_candidate:  string with min int from array of pair<string, ints>
string min_candidate(pair<string, int> *candidates, int n)
{
    int i, minsofar;
    string minstring;

    minstring = candidates[0].first;
    minsofar = candidates[0].second;
    for (i = 1; i < n; ++i) {
        if (candidates[i].second < minsofar) {
            minsofar = candidates[i].second;
            minstring = candidates[i].first;
        }
    }

    return minstring;
}

// get_score:  find the maximum number of remaining solutions over all
//             possible responses to the query s
//             this version takes a chunk and finds the guess with lowest score
//             from that chunk
void get_score(struct args *args)
{
    // parse the args struct
    vector<string> &chunk = *(args->chunk);
    vector<string> &perms = *(args->perms);
    pair<string, int> *cd = args->cd;
    int thread_id = args->thread_id;

    typedef vector<string>::const_iterator vec_iter;
    int sz = perms[0].size();

    pair<string, int> best_guess;
    best_guess.second = perms.size();
    int wc_num_remaining;
    for (vec_iter s = chunk.begin(); s != chunk.end(); ++s) {
        vector<int> matches(sz + 1, 0);
        for (vec_iter p = perms.begin(); p != perms.end(); ++p) {
            ++matches[evaluate(*s, *p)];
        }
        wc_num_remaining = *max_element(matches.begin(), matches.end());
        if (wc_num_remaining < best_guess.second) {
            best_guess.first = *s;
            best_guess.second = wc_num_remaining;
        }
    }

    cd[thread_id] = best_guess;

    return;
}

// wc_response:  the response to guess that eliminates the least solutions
int wc_response(string &guess, vector<string> &perms)
{
    map<int, int> matches_eval;

    for (vector<string>::iterator it = perms.begin(); it!=perms.end(); ++it) {
        ++matches_eval[evaluate(guess, *it)];
    }

    return max_element(matches_eval.begin(), matches_eval.end(), prcmp)->first;
}

// prcmp:  comparison function for pair<int, int> types in map
bool prcmp(pair<int, int> x, pair<int, int> y)
{
    return x.second < y.second;
}

void sequence_print(const string s)
{
    for (string::const_iterator i = s.begin(); i != s.end(); i++) {
        if (i == s.begin())
            cout << "(";
        cout << *i;
        if (i != s.end() - 1)
            cout << ", ";
        else
            cout << ")";
    }
}

나는 이것을 옮겼다. Are You The One 습니까? 업데이트되고 더 빠른 코드로 GitHub 에서.
Chris Chute
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.