Sen teksin? (Beyni Türev)


15

Senin için zor bir tane var!

Kız arkadaşım geçenlerde MTV'de (ABD) yeni bir şovla karşılaştı. Bu korkunç bir gösteri ve üzerindeki herkes değersiz ama "oyun" oldukça ilginç. Wikipedia'dan:

Sen teksin? mükemmel eşleşmelerini bulmak için Hawaii'de birlikte yaşayan 20 kişiyi takip ediyor. 10 erkek ve 10 kadın on mükemmel maçın hepsini on hafta içinde doğru bir şekilde seçebiliyorsa, aralarında bölmek için 1 milyon dolar kazanacaklar.

Şimdi oyun kısmı için (Wikipedia'dan da):

Oyuncu kadrosunun her bölümü, mükemmel eşleşmelerinin bir mücadelede rekabet etmek olduğuna inandıkları kişilerle eşleşecek. Yarışmayı kazananlar bir randevuya gidecek ve maçlarını hakikat standında test etme şansına sahip olacaklar. Oyuncular, mükemmel bir eşleşme olup olmadıklarını belirlemek için hakikat standına gitmek için kazanan çiftlerden birini seçecekler. Maçları onaylamanın tek yolu budur.Her bölüm, çiftlere kaç tane mükemmel eşleşmeleri olduğu, ancak hangi maçların doğru olmadığı söylenecekleri bir törenle sona erer.

TL; DR: Bu bir Mastermind türevidir (spesifik olarak M (10,10)). Oyunun kuralları aşağıdaki gibidir:

  1. 10'luk 2 set ile başlıyorsunuz, bunlara Set A diyelim: {A, B, C, D, E, F, G, H, I, J} ve Set 2: {1,2,3,4,5, 6,7,8,9,10}

  2. Bilgisayar, A kümesindeki üyelerin 1'e 1 eşleştirildiği {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10} şeklinde bir çözüm (sizin için görünmez) oluşturur Bir çözümün başka bir örneği {A2, B5, C10, D8, E1, F7, G6, H4, I9, J3} olabilir.

  3. İlk dönüşünüzden önce, seçtiğiniz tek bir çiftin doğru olup olmadığını sorarsınız. Sorunuz {A1} (örn. {C8}) biçiminde olacaktır ve 1 (doğru anlamına gelir) veya 0 (tahmininiz yanlış demektir) alırsınız.

  4. İlk gerçek sıranız. İlk tahmininizi {A1, B2, C3, D4, E5, F6, G7, H8, I9, J10} veya istediğiniz herhangi bir permütasyon şeklinde yaparsınız. Tahmininiz herhangi bir öğenin katlarını içeremez, yani {A1, A2, A3, A4, A5, B6, B7, B8, B9, B10} tahminleri geçerli bir tahmin DEĞİLDİR. Her turdan sonra, bilgisayar size doğru eşleşme sayısını söyler , ancak hangi eşleşmelerin doğru olduğunu DEĞİL.

  5. Her maç doğru olana kadar (yani 10'luk bir cevap) veya 10 hamleniz yukarı olana kadar (hangisi daha erkense) 3. ve 4. adımları tekrarlayın. 10. sıradan önce veya ondan sonra çözerseniz, 1 milyon dolar kazanırsınız. Aksi takdirde, kaybedersiniz ve bazı insanlar (veya harfler ve sayılar) sonsuzluğu 10 kedisiyle geçirmek için eve giderler.

Bu en kısa kod yarışması DEĞİLDİR. En az ortalama tahmin sayısında rastgele bir eşleşmeyi çözebilen kişi kazanan olacaktır. Akıllı oyun oynama ve hesaplama hızı da büyük olasılıkla faktör olacaktır. Ortalama dönüş sayısının neredeyse kesinlikle 10'dan fazla olacağını varsayıyorum, bu yüzden 1 milyon dolarlık ödülü kazanma olasılığınız (muhtemelen MTV tarafından ödeniyor), zayıf. Sadece nasıl o büyük ödülü kazanmak için döküm imkansızdır?

Not: {A1, B2, ...} biçimine koymak zorunlu değildir. Sadece bu formu soruda bulmacanın ne olduğunu kesinlikle netleştirmek için kullandım. Bu forma koymazsanız, lütfen nasıl arayacağınızı açıklayın.

İyi şanslar!


3
Kazanmak için en az ortalama tahmin olarak bunu çözebilir kişiyi istiyorsan, neden yapmaz o kazanan kriterlere? Mükemmel bir galibiyet koşulu karşısında bize baktığında bunun bir popülerlik yarışması olması için herhangi bir neden göremiyorum.
Geobits

Anlayabildiğim kadarıyla sorunun Mastermind'i en iyi şekilde oynamakla bir ilgisi yok. Bir kullanıcının oynamasına izin veren bir oyun ister.
feersum

1
O zaman pop-yarışması bunun etiketi değildir.

1
@ hosch250 Kazanan için güncellenmiş kriter
dberm22

2
7 oy, 2 favori ve yanıt yok. Bunun zor olduğunu biliyordum!
dberm22

Yanıtlar:


6

Python 2 (Pypy kullanarak çalıştırılırsa daha hızlı çalıştır)

Neredeyse her zaman 10 veya daha az turda doğru eşleştirmeyi tahmin ettiğine inanılıyor

Algoritmam beyni benim hobim olarak yanıtımdan alınmıştır (bkz. Ideone ). Fikir, en kötü durumda kalan olasılık sayısını en aza indiren tahminin bulunmasıdır. Altındaki algoritmam sadece kaba kuvvet uygular, ancak zaman kazanmak için, kalan olasılıkların sayısından daha büyükse rastgele tahmin seçer RANDOM_THRESHOLD. İşleri hızlandırmak veya daha iyi performans görmek için bu parametre ile oynayabilirsiniz.

Pypy kullanarak çalıştırırsanız algoritma oldukça yavaş, ortalama 10 saniye (normal CPython yorumlayıcısı kullanıyorsanız 30 saniye civarında) bu yüzden tüm permütasyonlarda test edemiyorum. Ancak performans oldukça iyi, yaklaşık 30 testten sonra 10 veya daha düşük turda doğru eşleştirmeyi bulamadığı bir örnek görmedim.

Her neyse, eğer bu gerçek hayat şovunda kullanılıyorsa, bir sonraki turdan önce (bir hafta?) Çok zaman var, bu yüzden bu algoritma gerçek hayatta kullanılabilir = D

Bu nedenle, bunun ortalama 10 veya daha düşük tahminlerde doğru eşleşmeleri bulacağını varsaymanın güvenli olduğunu düşünüyorum.

Kendin dene. Önümüzdeki birkaç gün içinde hızını artırabilir (EDIT: daha da geliştirmek zor görünüyor, bu yüzden sadece kodu olduğu gibi bırakacağım. Sadece rastgele seçim yapmaya çalıştım, ama hatta size=7, 5040 vakanın 3'ünde başarısız oluyor , bu yüzden akıllı yöntemi kullanmaya karar verdim). Şu şekilde çalıştırabilirsiniz:

pypy are_you_the_one.py 10

Veya sadece nasıl çalıştığını görmek istiyorsanız, daha küçük bir sayı girin (böylece daha hızlı çalışır)

Tam bir test çalıştırmak için (uyarı: size> 7 için çok uzun sürer ), negatif bir sayı girin.

İçin tam test size=7(2m 32s içinde tamamlandı):

...
(6, 5, 4, 1, 3, 2, 0): 5 tahmin
(6, 5, 4, 2, 0, 1, 3): 5 tahmin
(6, 5, 4, 2, 0, 3, 1): 4 tahmin
(6, 5, 4, 2, 1, 0, 3): 5 tahmin
(6, 5, 4, 2, 1, 3, 0): 6 tahmin
(6, 5, 4, 2, 3, 0, 1): 6 tahmin
(6, 5, 4, 2, 3, 1, 0): 6 tahmin
(6, 5, 4, 3, 0, 1, 2): 6 tahmin
(6, 5, 4, 3, 0, 2, 1): 3 tahmin
(6, 5, 4, 3, 1, 0, 2): 7 tahmin
(6, 5, 4, 3, 1, 2, 0): 7 tahmin
(6, 5, 4, 3, 2, 0, 1): 4 tahmin
(6, 5, 4, 3, 2, 1, 0): 7 tahmin
Ortalama sayım: 5.05
Maksimum sayı: 7
Min. Sayı: 1
Başarı sayısı: 5040

Eğer RANDOM_THRESHOLDve CLEVER_THRESHOLDher ikisi de çok yüksek bir değere (50000 gibi) ayarlanmışsa, algoritmayı en kötü durumda olasılık sayısını en aza indiren en uygun tahminin bulunması için zorlar. Bu çok yavaş ama çok güçlü. Örneğin, onu çalıştırmak size=6en fazla 5 turda doğru eşleştirmeleri bulabileceğini iddia eder.

Ortalama, yaklaşık olarak ortalamaya göre daha yüksek olmasına rağmen (ortalama 4.11 mermidir), ancak her zaman başarılı olur, daha da fazla, bir turdan fazla yedek kaldı. Bu da size=10, neredeyse her zaman 10 veya daha az turda doğru eşleşmeleri bulması gerektiği hipotezimizi daha da güçlendirir .

Sonuç (3m 9s'de tamamlandı):

(5, 4, 2, 1, 0, 3): 5 tahmin
(5, 4, 2, 1, 3, 0): 5 tahmin
(5, 4, 2, 3, 0, 1): 4 tahmin
(5, 4, 2, 3, 1, 0): 4 tahmin
(5, 4, 3, 0, 1, 2): 5 tahmin
(5, 4, 3, 0, 2, 1): 5 tahmin
(5, 4, 3, 1, 0, 2): 5 tahmin
(5, 4, 3, 1, 2, 0): 5 tahmin
(5, 4, 3, 2, 0, 1): 5 tahmin
(5, 4, 3, 2, 1, 0): 5 tahmin
Ortalama sayı: 4.41
Maksimum sayı: 5
Min. Sayı: 1
Başarı sayısı: 720

Kod.

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)

Bu gerçekten inanılmaz. 100 kez daha çalıştırdım ve çözümü bulmak için 10'dan fazla tahminde bulunulmadı. Bir çift 10 ve hatta bir çift 6 gördüm. (10 turun altında doğru eşlemeyi bulamadığı bir örnek görmediklerini söylüyorsunuz. Bu muhtemelen "10 veya daha az turda" demeli, ancak bu sadece anlambilimsel.) Bu harika! Lambda değerinizin, en uygun tahminde bulunmanıza izin veren bir çeşit entropi ölçümü olduğunu varsayıyorum, ancak nasıl veya nerede ayarlandığını göremiyorum. Bu sadece benim yoğunum, programınızın bir iddiası değil. İnanılmaz iş!
dberm22

En kötü durumda kalan len(remove_perms ...)kısım sayısını ( parça) en aza indirmeye çalışıyor . Ve evet, <= 10 mermi =) demek istedim. Btw yukarıdaki kodda gerçekten en uygun tahmin asla yapılmaz, çünkü koyduğumdan CLEVER_THRESHOLD=0, optimal tahmin bu kümenin dışında olsa da, sadece olasılıklar listesinden tahmin etmeye çalışacaktır. Ama zaman kazanmak için bunu devre dışı bırakmaya karar verdim. İçin size=7her zaman başarılı olduğunu gösteren tam test ekledim .
justhalf

1
Kodunuzu bir gecede 'Clever Threshold = 0' ile çalıştırıyorum ((9,8,7,6,5,4,3,2,1,0'den başlayarak) ve tüm permütasyonlarda geriye doğru çalışıyorum). Şimdiye kadar sadece 2050 yaşındayım, ancak 11 dönüş aldığı 13 vaka oldu. Örnek çıktı çıktı - (9, 8, 7, 4, 0, 6, 3, 2, 1, 5): 9 tahmin, Ortalama sayım: 8.29, Maksimum sayım: 11, Min sayım: 4, Num başarı: 2037, Num Eğer 'Akıllı Eşik' uygun şekilde ayarlanmışsa, bu 11'lerin 10'lar olacağına bahse girerim. Yine de, ortalama olarak, 8.3 oldukça muhteşem.
dberm22

@ dberm22: Evet, bu yavaş algoritmayı bir gecede çalıştırdığınız için teşekkür ederiz! Tam testi çalıştırdım size=8ve bu ayar kullanılırsa en son sürümün sadece 40308 başarısı olduğunu (40320 yerine) buldum. Ama bu hala% 99.97 başarı oranı! TV şovu organizatörünün iflas etmesini sağlayabiliriz.
justhalf

3

CJam -19 dönüş- Bir Idiot Stratejisi

Bu ciddi bir cevap değil, bir gösteri. Bu, bir salakın çözümü, dönüşün ikinci bölümünden sağlanan doğru eşleştirme bilgilerinin sayısını dikkate almadığı bir çözümdür. Tamamen rastgele eşlemelerle bu ortalama 27 hafta sürer. Bu cevap söylediğim gibi yetersiz ama akıllı bir grubun (bu programdan çok daha akıllı) olasılığının beklediğiniz kadar ince olmadığını gösteriyor. Daha akıllı algoritmalar yazdım, ancak çalıştırmak için daha fazla zaman ayırdım, böylece gerçekten onlardan cevap alabilirim.

Güncelleme: Aşağıdaki kod, yalnızca doğru olanların doğru olduğunu bildiğimiz olanlar işe yaramazsa kaldırılması gerektiğini belirtmek için güncellendi. Ayrıca rastgele "doğru cevap" jeneratörünü gösterecek şekilde düzenlendi. Ortalama sonuç sadece 19'dur. Hala aptalca bir çözümdür, ancak bir önceki marjinalden daha iyidir.

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

Ayrıca not: özensiz hata işleme, daha akıllı bir yöntem programlamak daha kolay olmasıdır.
kaine

Bir çözümü uygulamak için yeterince cesur olduğu için +1. Aslında doğru çözümü tahmin etmenin ortalama 27 dönüş alması beni oldukça şaşırttı. Doğru tahmin ettiğiniz gibi, sonraki maçların üstel olarak (iyi, faktöriyel) daha kolay bulunacağını varsayıyorum. Teşekkürler! Birinin 10'dan daha az olup olmadığını görmek isterim. Bana umut verdin!
dberm22

27 şaşırtıcıysa şuna bir bakın! Şaka bir yana bence 10 ya da en azından ortalama bir çözümün mümkün olduğunu düşünüyorum. Makul bir zaman diliminde çalışmak için böyle bir algoritma elde edemiyorum.
kaine

19 ... daha da şok oldum. Yine de bir soru ... programınızda, "İlk bilinmeyen için bir sayı tahmin eder. Doğru çifti ayarlarsa; başka bir şey onu siler" dediğiniz bir soru. Yanlışsa ... doğru olmadığını bildiğiniz eşleşmeler listesine eklemelisiniz, bu yüzden tekrar tahmin etmeyin (permütasyonda veya ayrı tahmin olarak).
dberm22

Olasılıklar listesinden silmek anlamına gelir; Muhtemel olanların bir listesi var, imkansız olanların bir listesi değil. Oh, ve ben bu erkek dizide pozisyon olduğunu ve dişi 0-9 (ya da tam tersi) sayıları olduğunu belirtmeyi unuttum. Daha ciddi bir sunum olsaydı A5 B2 vb. Biçimini kullanırdım.
kaine

3

Hızlı Çok Dişli C ++ Sürümü

Bu iş parçacığı etkin beri bir süre oldu biliyorum, ama paylaşmak için serin bir ek var: İşte Are You The One için minimax algoritmasının bir C ++ uygulaması var. , olası her tahminin değerlendirilmesini hızlandırmak için çoklu iş parçacığı kullanır.

Bu sürüm Python sürümünden çok daha hızlı (orijinal Python sürümü maksimuma ayarlandığında 100 kat daha hızlı RANDOM_THRESHOLD ve CLEVER_THRESHOLD). Herhangi bir rastgele tahmin kullanmaz, daha ziyade tüm permütasyonları değerlendirir ve mümkün olan en fazla sayıda çözümü (en kötü durum yanıtı göz önüne alındığında) ortadan kaldıran permütasyonu tahmin eder.

Daha küçük oyunlar için, "ayto -n" çağrısı oyunu tüm n'de çalıştıracaktır! olası gizli eşleşmeler ve size sonuçların kısa bir özetini verir.

10'un tümünü değerlendirmek hala zor olduğu için! Örneğin "ayto 10" adını verirseniz, simülatör sabit ilk üç tahminini yapar, ardından tahminini seçmek için minimax çalıştırır ve en kötü durum değerlendirmesinin verildiğini varsayar. Bu bizi algoritmaya tanımlamak için maksimum sayıda tahmin alan vektör sınıfında olan gizli bir vektöre "en kötü durum" yoluna götürür. Bu varsayım test edilmemiştir.

Kadar n = 9 , daha atmıştır bir simülasyon olmamıştır n çözümü için döner.

Bunu kendiniz test etmek için örnek bir derleme şöyledir:

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

İşte çıktı ile küçük bir örnek:

$ ./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.

kod

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

Bunu Sen misin? GitHub'da güncellenmiş, daha hızlı kod ile.
Chris Chute
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.