Bạn có phải là người duy nhất? (Đạo hàm chủ đạo)


15

Tôi đã có một khó khăn cho bạn!

Bạn gái của tôi gần đây đã bắt gặp một chương trình mới trên MTV (Hoa Kỳ). Đây là một chương trình khủng khiếp và mọi người trên đó đều là rác rưởi, nhưng "trò chơi" này khá thú vị. Từ Wikipedia:

Bạn có phải là người duy nhất? theo dõi 20 người đang sống cùng nhau ở Hawaii để tìm thấy sự kết hợp hoàn hảo của họ. Nếu 10 người đàn ông và 10 phụ nữ có thể chọn chính xác tất cả mười trận đấu hoàn hảo trong mười tuần, họ sẽ kiếm được 1 triệu đô la để chia cho họ.

Bây giờ cho phần trò chơi (cũng từ Wikipedia):

Mỗi tập phim, các diễn viên sẽ kết hợp với người mà họ tin rằng trận đấu hoàn hảo của họ là để cạnh tranh trong một thử thách. Những người chiến thắng trong thử thách sẽ hẹn hò và có cơ hội kiểm tra trận đấu của họ trong gian hàng sự thật. Các thành viên trong đoàn sẽ chọn một trong những cặp đôi chiến thắng để đến gian hàng sự thật để xác định xem họ có phải là một cặp đôi hoàn hảo hay không. Đây là cách duy nhất để xác nhận trận đấu. Mỗi tập kết thúc bằng một buổi lễ phù hợp, trong đó các cặp đôi sẽ được cho biết họ có bao nhiêu trận đấu hoàn hảo, nhưng không phải trận đấu nào là chính xác.

TL; DR: Đây là một dẫn xuất của Mastermind (M (10,10) để được cụ thể). Các quy tắc của trò chơi như sau:

  1. Bạn bắt đầu với 2 bộ 10, hãy gọi chúng là Bộ A: {A, B, C, D, E, F, G, H, I, J} và Bộ 2: {1,2,3,4,5, 6,7,8,9,10}

  2. Máy tính tạo ra một giải pháp (không hiển thị cho bạn) ở dạng {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10}, trong đó các thành viên trong bộ A được ánh xạ 1 đến 1 để đặt 2. Một ví dụ khác về giải pháp có thể là {A2, B5, C10, D8, E1, F7, G6, H4, I9, J3}.

  3. Trước lượt chơi đầu tiên của bạn, bạn có thể hỏi xem một cặp duy nhất, cụ thể mà bạn chọn có chính xác không. Câu hỏi của bạn sẽ ở dạng {A1} (ví dụ {C8}) và bạn nhận được 1 (có nghĩa là chính xác) hoặc 0 (có nghĩa là dự đoán của bạn không chính xác).

  4. Lần lượt thực tế đầu tiên của bạn. Bạn đưa ra dự đoán đầu tiên của mình dưới dạng {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10} hoặc bất kỳ hoán vị nào bạn chọn. Dự đoán của bạn không thể chứa bội số của bất kỳ mục nào, tức là đoán {A1, A2, A3, A4, A5, B6, B7, B8, B9, B10} KHÔNG phải là dự đoán hợp lệ. Sau mỗi lượt, máy tính sẽ cho bạn biết số lượng khớp chính xác, nhưng KHÔNG khớp chính xác.

  5. Lặp lại các bước 3 và 4 cho đến khi bạn nhận được mọi kết quả khớp chính xác (tức là phản hồi là 10) hoặc cho đến khi 10 bước di chuyển của bạn kết thúc (tùy theo điều kiện nào sớm hơn). Nếu bạn giải quyết nó trước hoặc trong lượt thứ 10 của mình, bạn sẽ giành được 1 triệu đô la. Nếu không, bạn thua, và một số người (hoặc thư và số) về nhà một mình để dành sự sống vĩnh cửu với 10 con mèo của họ.

Đây KHÔNG phải là một cuộc thi mã ngắn nhất. Người có thể giải một trận đấu ngẫu nhiên với số lần đoán trung bình ít nhất sẽ là người chiến thắng. Tốc độ tính toán và chơi trò chơi thông minh cũng có thể là yếu tố. Tôi giả sử số lượt quay trung bình gần như chắc chắn sẽ lớn hơn 10, do đó, tỷ lệ bạn giành được giải thưởng trị giá 1 triệu đô la (có lẽ do MTV trả, không phải tôi) là rất nhỏ. chỉ cần như thế nào không thể là nó cho các diễn viên để giành chiến thắng giải thưởng lớn?

Lưu ý: Không nhất thiết phải đặt nó ở định dạng {A1, B2, ...}. Tôi chỉ đơn giản sử dụng hình thức đó trong câu hỏi để làm cho nó hoàn toàn rõ ràng câu đố là gì. Nếu bạn không đặt nó ở dạng này, vui lòng chỉ giải thích cách gọi nó.

Chúc may mắn!


3
Nếu bạn muốn người có thể giải quyết nó trong những lần đoán trung bình ít nhất sẽ giành chiến thắng, tại sao không biến điều đó thành tiêu chí chiến thắng? Tôi không thể thấy bất kỳ lý do nào đây sẽ là một cuộc thi phổ biến khi một điều kiện giành chiến thắng hoàn toàn hợp lệ đang đối mặt với chúng tôi.
Geobits

Theo như tôi có thể nói câu hỏi không liên quan gì đến việc chơi Mastermind một cách tối ưu. Nó yêu cầu một trò chơi cho phép người dùng chơi nó.
frageum

1
Sau đó, cuộc thi pop không phải là thẻ cho điều này.
Hosch250

1
@ hosch250 Tiêu chí cập nhật cho người chiến thắng
dberm22

2
7 upvote, 2 yêu thích, và không có câu trả lời. Tôi biết đây là một khó khăn!
dberm22

Câu trả lời:


6

Python 2 (chạy nhanh hơn nếu chạy bằng Pypy)

Được tin là hầu như luôn luôn đoán đúng cặp trong 10 vòng hoặc thấp hơn

Thuật toán của tôi được lấy từ câu trả lời của tôi cho chủ mưu là sở thích của tôi (xem trong Ideone ). Ý tưởng là tìm ra dự đoán giúp giảm thiểu số lượng khả năng còn lại trong trường hợp xấu nhất. Thuật toán của tôi dưới đây chỉ đơn giản là ép buộc nó, nhưng để tiết kiệm thời gian, nó chỉ cần chọn dự đoán ngẫu nhiên nếu số khả năng còn lại lớn hơnRANDOM_THRESHOLD . Bạn có thể chơi xung quanh với tham số này để tăng tốc mọi thứ hoặc để xem hiệu suất tốt hơn.

Thuật toán khá chậm, trung bình 10 giây cho một lần chạy nếu chạy bằng Pypy (nếu sử dụng trình thông dịch CPython bình thường thì khoảng 30 giây) vì vậy tôi không thể kiểm tra nó trên toàn bộ hoán vị. Nhưng hiệu suất khá tốt, sau khoảng 30 thử nghiệm, tôi chưa thấy trường hợp nào không thể tìm thấy cặp đôi chính xác trong 10 vòng hoặc thấp hơn.

Dù sao, nếu điều này được sử dụng trong chương trình thực tế, nó có nhiều thời gian trước vòng tiếp theo (một tuần?) Để thuật toán này có thể được sử dụng trong cuộc sống thực = D

Vì vậy, tôi nghĩ thật an toàn khi cho rằng trung bình điều này sẽ tìm thấy các cặp chính xác trong 10 lần đoán hoặc thấp hơn.

Hãy thử nó. Tôi có thể cải thiện tốc độ trong vài ngày tới (EDIT: có vẻ khó cải thiện hơn nữa, vì vậy tôi sẽ chỉ để lại mã như vậy. Tôi đã thử chỉ chọn ngẫu nhiên, nhưng ngay cả size=7, nó đã thất bại trong 3 trong số 5040 trường hợp , vì vậy tôi quyết định giữ phương pháp thông minh hơn). Bạn có thể chạy nó dưới dạng:

pypy are_you_the_one.py 10

Hoặc, nếu bạn chỉ muốn xem nó hoạt động như thế nào, hãy nhập số nhỏ hơn (để nó chạy nhanh hơn)

Để chạy thử nghiệm đầy đủ (cảnh báo: sẽ mất rất nhiều thời gian cho size > 7), đặt số âm.

Kiểm tra đầy đủ cho size=7 (hoàn thành trong 2m 32s):

...
(6, 5, 4, 1, 3, 2, 0): 5 lần đoán
(6, 5, 4, 2, 0, 1, 3): 5 lần đoán
(6, 5, 4, 2, 0, 3, 1): 4 lần đoán
(6, 5, 4, 2, 1, 0, 3): 5 lần đoán
(6, 5, 4, 2, 1, 3, 0): 6 lần đoán
(6, 5, 4, 2, 3, 0, 1): 6 lần đoán
(6, 5, 4, 2, 3, 1, 0): 6 lần đoán
(6, 5, 4, 3, 0, 1, 2): 6 lần đoán
(6, 5, 4, 3, 0, 2, 1): 3 lần đoán
(6, 5, 4, 3, 1, 0, 2): 7 lần đoán
(6, 5, 4, 3, 1, 2, 0): 7 lần đoán
(6, 5, 4, 3, 2, 0, 1): 4 lần đoán
(6, 5, 4, 3, 2, 1, 0): 7 lần đoán
Số lượng trung bình: 5,05
Số lượng tối đa: 7
Số lượng tối thiểu: 1
Số thành công: 5040

Nếu RANDOM_THRESHOLDCLEVER_THRESHOLDcả hai đều được đặt thành một giá trị rất cao (như 50000), nó sẽ buộc thuật toán tìm ra dự đoán tối ưu giúp giảm thiểu số lượng khả năng trong trường hợp xấu nhất. Điều này rất chậm, nhưng rất mạnh mẽ. Ví dụ: chạy nó trên size=6khẳng định rằng nó có thể tìm thấy các cặp chính xác trong tối đa 5 vòng.

Mặc dù trung bình cao hơn so với việc sử dụng xấp xỉ (trung bình là 4,11 vòng), nhưng nó luôn thành công, thậm chí nhiều hơn với một vòng còn lại để dự phòng. Điều này càng củng cố giả thuyết của chúng tôi rằng khi nào size=10, nó hầu như luôn luôn phải tìm ra các cặp chính xác trong 10 vòng hoặc ít hơn.

Kết quả (hoàn thành trong 3 giây 9 giây):

(5, 4, 2, 1, 0, 3): 5 lần đoán
(5, 4, 2, 1, 3, 0): 5 lần đoán
(5, 4, 2, 3, 0, 1): 4 lần đoán
(5, 4, 2, 3, 1, 0): 4 lần đoán
(5, 4, 3, 0, 1, 2): 5 lần đoán
(5, 4, 3, 0, 2, 1): 5 lần đoán
(5, 4, 3, 1, 0, 2): 5 lần đoán
(5, 4, 3, 1, 2, 0): 5 lần đoán
(5, 4, 3, 2, 0, 1): 5 lần đoán
(5, 4, 3, 2, 1, 0): 5 lần đoán
Số lượng trung bình: 4,41
Số lượng tối đa: 5
Số lượng tối thiểu: 1
Số thành công: 720

Mật mã.

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)

Điều này thực sự đáng kinh ngạc. Tôi đã chạy nó hơn 100 lần, và nó vẫn chưa mất hơn 10 lần đoán để tìm giải pháp. Tôi đã thấy một cặp đôi 10, và thậm chí một cặp 6. (Bạn nói rằng bạn chưa thấy trường hợp nào không thể tìm thấy cặp đôi chính xác dưới 10 vòng. Điều đó có thể nói là "trong 10 vòng hoặc ít hơn", nhưng đó chỉ là ngữ nghĩa.) Điều này thật tuyệt vời! Tôi cho rằng giá trị lambda của bạn là một số phép đo entropy cho phép bạn đưa ra dự đoán tối ưu, nhưng tôi không thấy nó được đặt ở đâu và như thế nào. Đây chỉ là tôi dày đặc, không phải là một bản cáo trạng của chương trình của bạn. Công việc đáng kinh ngạc!
dberm22

Đó là cố gắng giảm thiểu số lượng khả năng còn lại trong trường hợp xấu nhất ( len(remove_perms ...)phần). Và vâng, ý tôi là trong <= 10 vòng =). Btw trong mã trên dự đoán thực sự tối ưu không bao giờ được thực hiện, vì tôi đặt CLEVER_THRESHOLD=0, có nghĩa là nó sẽ chỉ cố gắng đoán từ danh sách các khả năng, mặc dù dự đoán tối ưu có thể nằm ngoài bộ này. Nhưng tôi quyết định vô hiệu hóa nó để tiết kiệm thời gian. Tôi đã thêm thử nghiệm đầy đủ cho size=7thấy rằng nó luôn thành công.
justhalf

1
Tôi đã chạy mã của bạn qua đêm với 'Ngưỡng thông minh = 0' (bắt đầu từ (9,8,7,6,5,4,3,2,1,0) và làm việc ngược qua tất cả các hoán vị). Tôi chỉ mới 2050 cho đến nay, nhưng đã có 13 trường hợp phải thực hiện 11 lượt. Mẫu in ra - (9, 8, 7, 4, 0, 6, 3, 2, 1, 5): 9 lần đoán, Số trung bình: 8,29, Số lượng tối đa: 11, Số lượng tối thiểu: 4, Số thành công: 2037, Num đã đánh giá: 2050. Nếu 'Ngưỡng thông minh' được đặt đúng, tôi cá rằng 11 số đó sẽ trở thành 10. Tuy nhiên, trung bình, 8.3 là khá tuyệt vời.
dberm22

@ dberm22: Vâng, cảm ơn bạn đã chạy thuật toán chậm này qua đêm! Tôi đã chạy thử nghiệm đầy đủ size=8và thấy rằng phiên bản mới nhất chỉ có 40308 thành công (thay vì 40320) nếu cài đặt này được sử dụng. Nhưng đó vẫn là tỷ lệ thành công 99,97%! Đoán chúng ta có thể làm cho nhà tổ chức chương trình truyền hình bị phá sản.
cần

3

CJam -19 lượt- Chiến lược của một thằng ngốc

Đây không phải là một câu trả lời nghiêm túc mà là một cuộc biểu tình. Đây là giải pháp của một thằng ngốc mà anh ta không tính đến số lượng thông tin ghép nối chính xác được cung cấp từ phần thứ hai của lượt chơi. Với các cặp hoàn toàn ngẫu nhiên, việc này mất trung bình 27 tuần. Câu trả lời này không phù hợp như tôi đã nói nhưng chỉ ra rằng tỷ lệ cược cho một nhóm không liên quan (không liên quan nhiều đến chương trình này) có thể không mỏng như bạn mong đợi. Các thuật toán không liên quan hơn tôi đã viết, tuy nhiên phải mất nhiều thời gian hơn để chạy để tôi thực sự có thể nhận được câu trả lời từ chúng.

Cập nhật: Mã bên dưới đã được cập nhật để sử dụng trạng thái sẽ loại bỏ những mã không hoạt động nếu chỉ đúng là những cái chúng ta đã biết là chính xác. Nó cũng được chỉnh sửa để hiển thị trình tạo "câu trả lời đúng" ngẫu nhiên của tôi. Kết quả trung bình bây giờ chỉ là 19. Nó vẫn là một giải pháp ngu ngốc nhưng tốt hơn so với trước đây.

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

Cũng lưu ý: việc xử lý lỗi cẩu thả là bởi vì nó dễ dàng hơn để lập trình mà sau đó là một phương pháp không phù hợp hơn.
kaine

+1 cho việc thực sự đủ can đảm để thực hiện một giải pháp. Tôi thực sự khá sốc khi chỉ mất trung bình 27 lượt để đoán đúng giải pháp. Tôi cho rằng như bạn đoán chính xác, các trận đấu tiếp theo dễ dàng hơn theo cấp số nhân (tốt, theo yếu tố). Cảm ơn! Tôi muốn biết liệu có ai có thể nhận được ít hơn 10. Bạn đã cho tôi hy vọng!
dberm22

Nếu 27 là đáng ngạc nhiên nhìn vào đó! Nói đùa, tôi nghĩ rằng một giải pháp mà trung bình 10 hoặc ít nhất là có được nó là trung bình là có thể. Tôi không thể có được một thuật toán như vậy để làm việc trong một khung thời gian hợp lý.
kaine

19 ... Tôi thậm chí còn sốc hơn. Tuy nhiên, chỉ là một câu hỏi ... trong chương trình của bạn, trong đó bạn nói "Đoán một số cho ẩn số đầu tiên. Nếu đúng đặt cặp; người khác sẽ xóa nó". Nếu nó sai ... bạn nên thêm nó vào danh sách các trận đấu mà bạn biết là không chính xác, vì vậy bạn không đoán lại lần nữa (theo cách hoán vị hoặc theo cách đoán riêng).
dberm22

Nó có nghĩa là xóa nó khỏi danh sách các khả năng; Tôi có một danh sách những cái có thể không phải là danh sách những cái không thể. Ồ, và tôi đã quên đề cập rằng điều này có vị trí nam trong mảng và nữ là số 0-9 (hoặc ngược lại). Tôi sẽ sử dụng định dạng A5 B2, vv nếu đó là một bài nghiêm túc hơn.
kaine

3

Phiên bản C ++ đa luồng nhanh

Tôi biết đã được một thời gian kể từ khi chủ đề này hoạt động, nhưng tôi có một bổ sung thú vị để chia sẻ: Đây là một triển khai C ++ của thuật toán minimax cho Are You The One? , trong đó sử dụng đa luồng để tăng tốc độ đánh giá từng dự đoán có thể.

Phiên bản này nhanh hơn nhiều so với phiên bản Python (nhanh hơn 100 lần khi phiên bản Python gốc được đặt thành tối đa RANDOM_THRESHOLDCLEVER_THRESHOLD). Nó không sử dụng bất kỳ phỏng đoán ngẫu nhiên nào, mà chỉ đánh giá tất cả các hoán vị và đệ trình như đoán của nó hoán vị loại bỏ số lượng lớn nhất các giải pháp có thể (đưa ra phản ứng trong trường hợp xấu nhất).

Đối với các trò chơi nhỏ hơn, gọi "ayto -n" sẽ chạy trò chơi trên tất cả n! kết quả ẩn có thể, và sẽ cung cấp cho bạn một bản tóm tắt ngắn về kết quả ở cuối.

Vì nó vẫn không thể đánh giá cả 10! ví dụ, các kết quả ẩn có thể có, nếu bạn gọi "ayto 10", ví dụ, trình giả lập thực hiện ba lần đoán đầu tiên cố định của nó, sau đó chạy minimax để chọn dự đoán của nó và cho rằng nó được đưa ra đánh giá trong trường hợp xấu nhất. Điều này dẫn chúng ta xuống một "đường dẫn trường hợp xấu nhất" đến một vectơ ẩn có lẽ là trong lớp vectơ có thuật toán lấy số lượng dự đoán tối đa để xác định. Phỏng đoán này chưa được thử nghiệm.

Lên đến n = 9 , chưa có một mô phỏng nào mất hơn n lượt để giải.

Để tự kiểm tra điều này, một phần tổng hợp ví dụ sẽ như sau:

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

Đây là một ví dụ nhỏ với đầu ra:

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

Tôi đã chuyển cái này thành Are You The One? trên GitHub với mã cập nhật, nhanh hơn.
Chris Chute
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.