คุณเป็นคนหนึ่งหรือไม่ (ผู้บงการอนุพันธ์)


15

ฉันมีสิ่งที่ยากสำหรับคุณ!

แฟนของฉันเพิ่งเจอรายการใหม่ใน MTV (USA) มันเป็นการแสดงที่แย่มากและทุกคนในนั้นดูไร้ค่า แต่ "เกม" น่าสนใจทีเดียว จาก Wikipedia:

คุณเป็นคนหนึ่งหรือไม่ ติดตาม 20 คนที่อาศัยอยู่ด้วยกันในฮาวายเพื่อค้นหาคู่แท้ของพวกเขา หากผู้ชาย 10 คนและผู้หญิง 10 คนสามารถเลือกการแข่งขันที่สมบูรณ์แบบทั้งสิบรายการได้อย่างถูกต้องภายในสิบสัปดาห์พวกเขาจะได้รับ $ 1 ล้านเพื่อแยกพวกเขา

ตอนนี้สำหรับส่วนของเกม (เช่นจาก Wikipedia):

แต่ละตอนที่นักแสดงจะจับคู่กับผู้ที่พวกเขาเชื่อว่าคู่ที่สมบูรณ์แบบของพวกเขาคือการแข่งขันในการท้าทาย ผู้ชนะการแข่งขันจะออกเดทและมีโอกาสทดสอบการแข่งขันในบูธจริง สมาชิกนักแสดงจะเลือกคู่ที่ชนะเลิศหนึ่งคนเพื่อไปที่บูธความจริงเพื่อพิจารณาว่าพวกเขาเป็นคู่ที่สมบูรณ์แบบหรือไม่ นี่เป็นวิธีเดียวที่จะยืนยันการแข่งขัน แต่ละตอนจบลงด้วยพิธีจับคู่ที่คู่รักจะได้รับการบอกว่ามีคู่ที่สมบูรณ์แบบจำนวนมาก แต่ไม่ตรงกับที่ตรงกัน

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,000,000 ไม่เช่นนั้นคุณก็แพ้และบางคน (หรือตัวอักษรและตัวเลข) กลับบ้านคนเดียวเพื่อใช้เวลากับแมว 10 ตัว

นี่ไม่ใช่การประกวดรหัสสั้นที่สุด คนที่สามารถแก้ปัญหาการจับคู่แบบสุ่มในอย่างน้อยเฉลี่ยจำนวนคาดเดาจะเป็นผู้ชนะ การเล่นเกมที่ชาญฉลาดและความเร็วในการคำนวณจะเป็นปัจจัยที่สำคัญเช่นกันฉันคาดว่าจำนวนรอบการเลี้ยวโดยเฉลี่ยจะมากกว่า 10 แน่นอนดังนั้นโอกาสที่คุณจะชนะรางวัล $ 1 ล้าน (จ่ายโดย MTV ไม่ใช่ฉัน) เพียงแค่วิธีการที่เป็นไปไม่ได้ก็คือสำหรับนักแสดงที่จะชนะรางวัลแกรนด์?

หมายเหตุ: ไม่จำเป็นต้องวางในรูปแบบ {A1, B2, ... } ฉันแค่ใช้แบบฟอร์มนั้นในคำถามเพื่อให้ชัดเจนว่าตัวต่อคืออะไร หากคุณไม่ได้ใส่ไว้ในแบบฟอร์มนี้โปรดอธิบายวิธีการโทร

โชคดี!


3
หากคุณต้องการให้คนที่สามารถแก้ปัญหาได้ในการคาดคะเนค่าเฉลี่ยน้อยที่สุดที่จะชนะทำไมไม่ทำเช่นนั้นเกณฑ์การชนะ ฉันไม่เห็นเหตุผลใด ๆ ที่จะเป็นการประกวดความนิยมเมื่อเงื่อนไขการชนะที่สมบูรณ์แบบกำลังจ้องมองเราอยู่ตรงหน้า
Geobits

เท่าที่ฉันสามารถบอกได้ว่าคำถามนั้นไม่มีส่วนเกี่ยวข้องกับการเล่นบงการที่ดีที่สุด มันถามหาเกมที่อนุญาตให้ผู้ใช้เล่น
feersum

1
จากนั้นการประกวดป๊อปไม่ใช่แท็กสำหรับสิ่งนี้

1
@ hosch250 อัปเดตเกณฑ์สำหรับผู้ชนะ
dberm22

2
7 upvotes, 2 รายการโปรดและไม่มีคำตอบ ฉันรู้ว่านี่เป็นงานที่ยาก!
dberm22

คำตอบ:


6

Python 2 (ทำงานได้เร็วขึ้นหากใช้งาน Pypy)

เชื่อกันว่าจะเดาการจับคู่ที่ถูกต้องใน 10 รอบหรือต่ำกว่าเสมอ

อัลกอริทึมของฉันมาจากคำตอบของฉันสำหรับผู้บงการเป็นงานอดิเรกของฉัน (ดูในIdeone ) ความคิดคือการค้นหาการเดาที่ลดจำนวนความเป็นไปได้ที่เหลืออยู่ในกรณีที่เลวร้ายที่สุด ขั้นตอนวิธีการด้านล่างของฉันกำลังดุร้ายเพียง RANDOM_THRESHOLDแต่เพื่อประหยัดเวลาก็เพียงแค่เลือกสุ่มเดาถ้าจำนวนของความเป็นไปได้ที่เหลือมีขนาดใหญ่กว่า คุณสามารถเล่นโดยใช้พารามิเตอร์นี้เพื่อเร่งความเร็วหรือดูประสิทธิภาพที่ดีขึ้น

อัลกอริทึมค่อนข้างช้าโดยเฉลี่ย 10 วินาทีสำหรับการรันหนึ่งครั้งหากใช้งาน Pypy (ถ้าใช้ล่าม CPython ปกติมันอยู่ที่ประมาณ 30 วินาที) ดังนั้นฉันจึงไม่สามารถทดสอบกับการเปลี่ยนลำดับทั้งหมดได้ แต่ประสิทธิภาพค่อนข้างดีหลังจากการทดสอบประมาณ 30 ครั้งฉันไม่เห็นอินสแตนซ์ใด ๆ ที่ไม่สามารถหาการจับคู่ที่ถูกต้องใน 10 รอบหรือต่ำกว่า

อย่างไรก็ตามถ้าใช้ในการแสดงในชีวิตจริงมันมีเวลาอีกมากก่อนรอบถัดไป (หนึ่งสัปดาห์?) ดังนั้นอัลกอริทึมนี้สามารถใช้ในชีวิตจริง = D

ดังนั้นฉันคิดว่ามันปลอดภัยที่จะสมมติว่าโดยเฉลี่ยแล้วจะพบการจับคู่ที่ถูกต้องใน 10 ครั้งเดาหรือต่ำกว่า

ลองด้วยตัวคุณเอง ฉันอาจปรับปรุงความเร็วในอีกไม่กี่วันข้างหน้า (แก้ไข: มันดูเหมือนยากที่จะปรับปรุงต่อไปดังนั้นฉันจะทิ้งรหัสตามเดิมฉันพยายามเพียงเลือกแบบสุ่ม แต่ถึงsize=7อย่างนั้นมันล้มเหลวใน 3 จาก 5040 กรณี ดังนั้นฉันจึงตัดสินใจใช้วิธีที่ฉลาดกว่าเดิม) คุณสามารถเรียกใช้เป็น:

pypy are_you_the_one.py 10

หรือถ้าคุณแค่ต้องการดูว่ามันทำงานอย่างไรให้ใส่จำนวนที่น้อยลง

หากต้องการทดสอบเต็มรูปแบบ (คำเตือน: ใช้เวลานานกว่าsize> 7) ให้ใส่จำนวนลบ

การทดสอบเต็มรูปแบบสำหรับsize=7(เสร็จสมบูรณ์ใน 2m 32s):

...
(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_THRESHOLDและCLEVER_THRESHOLDตั้งค่าไว้สูงมาก (เช่น 50,000) มันจะบังคับให้อัลกอริทึมค้นหาการเดาที่ดีที่สุดซึ่งจะลดจำนวนความเป็นไปได้ในกรณีที่เลวร้ายที่สุด มันช้ามาก แต่ทรงพลังมาก ตัวอย่างเช่นการเรียกใช้โดยsize=6อ้างว่าสามารถค้นหาการจับคู่ที่ถูกต้องสูงสุด 5 รอบ

แม้ว่าค่าเฉลี่ยจะสูงกว่าเมื่อเทียบกับการใช้การประมาณ (ซึ่งคือ 4.11 โดยเฉลี่ย) แต่ก็ประสบความสำเร็จเสมอยิ่งมากขึ้นโดยเหลืออีกหนึ่งรอบที่เหลือ นี่ทำให้สมมติฐานของเราแข็งแกร่งขึ้นอีกว่าเมื่อใดsize=10ควรหาการจับคู่ที่ถูกต้องในรอบ 10 หรือน้อยกว่า

ผลลัพธ์ (สร้างเสร็จใน 3m 9s):

(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
ความสำเร็จของ NUM: 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 จนถึงตอนนี้ แต่มี 13 กรณีที่มันเลี้ยว 11 ครั้ง ตัวอย่างงานพิมพ์ - (9, 8, 7, 4, 0, 6, 3, 2, 1, 5): 9 ครั้ง, จำนวนเฉลี่ย: 8.29, จำนวนสูงสุด: 11, จำนวนขั้นต่ำ: 4, ความสำเร็จของ NUM: 2037, Num ประเมิน: 2050 หากตั้งค่า 'กำหนดเกณฑ์อย่างเหมาะสม' ฉันวางเดิมพัน 11 เหล่านั้นจะกลายเป็น 10 ยังคงโดยเฉลี่ย 8.3 สวยมาก
dberm22

@ dberm22: ใช่ขอบคุณที่ใช้อัลกอริทึมช้านี้ค้างคืน! ฉันรันการทดสอบอย่างสมบูรณ์size=8และพบว่าเวอร์ชันล่าสุดมีเพียง 40308 สำเร็จ (แทน 40320) หากใช้การตั้งค่านี้ แต่นั่นยังคงอัตราความสำเร็จ 99.97%! คิดว่าเราสามารถทำให้ผู้จัดรายการทีวีล้มละลายได้
justhalf

3

CJam -19 เปลี่ยน - กลยุทธ์ของคนโง่

นี่ไม่ใช่คำตอบที่จริงจัง แต่เป็นการสาธิต นี่เป็นวิธีแก้ปัญหาของคนงี่เง่าที่เขาไม่ได้คำนึงถึงจำนวนข้อมูลการจับคู่ที่ถูกต้องที่ได้รับจากส่วนที่สองของเทิร์น ด้วยการจับคู่แบบสุ่มทั้งหมดจะใช้เวลาเฉลี่ย 27 สัปดาห์ คำตอบนี้ไม่ได้เป็นอย่างที่ฉันได้กล่าว แต่บ่งชี้ว่าโอกาสสำหรับกลุ่ม intellegent (intellegent มากขึ้นว่าโปรแกรมนี้) มีแนวโน้มที่จะไม่เป็นบางอย่างที่คุณอาจคาดหวัง อัลกอริทึม intellegent เพิ่มเติมที่ฉันได้เขียน แต่ใช้เวลามากขึ้นในการทำงานเพื่อที่ฉันจะได้รับคำตอบจากพวกเขา

อัปเดต:รหัสด้านล่างนี้ได้รับการอัปเดตเพื่อใช้สถานะที่ควรลบรหัสที่ไม่ทำงานหากสิ่งที่ถูกต้องเพียงอย่างเดียวคือสิ่งที่เรารู้แล้วว่าถูกต้อง มันยังได้รับการแก้ไขเพื่อแสดงเครื่องกำเนิดไฟฟ้า ผลลัพธ์โดยเฉลี่ยอยู่ที่ 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 The One หรือไม่? ซึ่งใช้มัลติเธรดเพื่อเร่งการประเมินผลการเดาที่เป็นไปได้ในแต่ละครั้ง

รุ่นนี้เร็วกว่ารุ่น Python มาก (เร็วกว่า 100 เท่าเมื่อตั้งค่า Python ดั้งเดิมเป็นสูงสุดRANDOM_THRESHOLDและCLEVER_THRESHOLD) มันไม่ได้ใช้การคาดเดาแบบสุ่ม แต่จะประเมินการเรียงสับเปลี่ยนทั้งหมดและส่งเป็นการคาดเดาการเปลี่ยนแปลงที่กำจัดจำนวนที่มากที่สุดของการแก้ปัญหาที่เป็นไปได้ (ให้การตอบสนองกรณีที่เลวร้ายที่สุด)

สำหรับเกมที่เล็กกว่าการเรียก "ayto -n" จะเรียกใช้เกมบน n ทั้งหมด! การจับคู่ที่ซ่อนอยู่ที่เป็นไปได้และจะให้ข้อมูลสรุปสั้น ๆ แก่คุณในตอนท้าย

เนื่องจากมันยังยากที่จะประเมินทั้ง 10! การจับคู่ที่ซ่อนอยู่ที่เป็นไปได้เช่นถ้าคุณเรียกว่า "ayto 10" ตัวจำลองจะทำการคาดเดาสามครั้งแรกจากนั้นจึงเรียกใช้ 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ด้วยโค้ดที่อัปเดตเร็วขึ้น
Chris Chute
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.