Python 2 (berjalan lebih cepat jika dijalankan menggunakan Pypy)
Diyakini hampir selalu menebak pasangan yang benar dalam 10 putaran atau lebih rendah
Algoritme saya diambil dari jawaban saya untuk dalang sebagai hobi saya (lihat di Ideone ). Idenya adalah untuk menemukan tebakan yang meminimalkan jumlah kemungkinan yang tersisa dalam kasus terburuk. Algoritme saya di bawah ini hanya memaksa, tetapi untuk menghemat waktu, pilih saja tebakan acak jika jumlah kemungkinan yang tersisa lebih besar dari RANDOM_THRESHOLD
. Anda dapat bermain-main dengan parameter ini untuk mempercepat atau untuk melihat kinerja yang lebih baik.
Algoritma ini sangat lambat, rata-rata 10s untuk satu kali dijalankan jika dijalankan menggunakan Pypy (jika menggunakan juru bahasa CPython normal sekitar 30-an) jadi saya tidak dapat mengujinya pada seluruh permutasi. Tetapi kinerjanya cukup baik, setelah sekitar 30 tes saya belum melihat contoh di mana ia tidak dapat menemukan pasangan yang benar dalam 10 putaran atau lebih rendah.
Lagi pula, jika ini digunakan dalam pertunjukan kehidupan nyata, ia memiliki banyak waktu sebelum babak berikutnya (satu minggu?) Sehingga algoritma ini dapat digunakan dalam kehidupan nyata = D
Jadi saya pikir aman untuk mengasumsikan bahwa rata-rata ini akan menemukan pasangan yang benar dalam 10 tebakan atau lebih rendah.
Cobalah sendiri. Saya mungkin meningkatkan kecepatan dalam beberapa hari ke depan (EDIT: tampaknya sulit untuk lebih meningkatkan, jadi saya hanya akan meninggalkan kode apa adanya. Saya hanya mencoba melakukan pengambilan acak, tetapi bahkan pada size=7
, gagal dalam 3 dari 5040 kasus , jadi saya memutuskan untuk tetap menggunakan metode yang lebih pintar). Anda dapat menjalankannya sebagai:
pypy are_you_the_one.py 10
Atau, jika Anda hanya ingin melihat cara kerjanya, masukkan angka yang lebih kecil (agar dapat berjalan lebih cepat)
Untuk menjalankan tes penuh (peringatan: akan sangat lama size
> 7), tulis angka negatif.
Tes penuh untuk size=7
(diselesaikan dalam 2m 32s):
...
(6, 5, 4, 1, 3, 2, 0): 5 tebakan
(6, 5, 4, 2, 0, 1, 3): 5 tebakan
(6, 5, 4, 2, 0, 3, 1): 4 tebakan
(6, 5, 4, 2, 1, 0, 3): 5 tebakan
(6, 5, 4, 2, 1, 3, 0): 6 tebakan
(6, 5, 4, 2, 3, 0, 1): 6 tebakan
(6, 5, 4, 2, 3, 1, 0): 6 tebakan
(6, 5, 4, 3, 0, 1, 2): 6 tebakan
(6, 5, 4, 3, 0, 2, 1): 3 tebakan
(6, 5, 4, 3, 1, 0, 2): 7 tebakan
(6, 5, 4, 3, 1, 2, 0): 7 tebakan
(6, 5, 4, 3, 2, 0, 1): 4 tebakan
(6, 5, 4, 3, 2, 1, 0): 7 tebakan
Jumlah rata-rata: 5,05
Jumlah maksimal: 7
Jumlah minimum: 1
Jumlah sukses: 5040
Jika RANDOM_THRESHOLD
dan CLEVER_THRESHOLD
keduanya diatur ke nilai yang sangat tinggi (seperti 50.000), itu akan memaksa algoritma untuk menemukan tebakan optimal yang meminimalkan jumlah kemungkinan dalam kasus terburuk. Ini sangat lambat, tetapi sangat kuat. Misalnya, menjalankannya dengan size=6
menyatakan bahwa ia dapat menemukan pasangan yang benar dalam maksimum 5 putaran.
Meskipun rata-rata lebih tinggi dibandingkan dengan menggunakan perkiraan (yang rata-rata adalah 4,11 putaran), tetapi selalu berhasil, bahkan lebih dengan satu putaran tersisa untuk cadangan. Ini semakin memperkuat hipotesis kami bahwa ketika size=10
, hampir selalu harus menemukan pasangan yang benar dalam 10 putaran atau kurang.
Hasilnya (selesai dalam 3m 9d):
(5, 4, 2, 1, 0, 3): 5 tebakan
(5, 4, 2, 1, 3, 0): 5 tebakan
(5, 4, 2, 3, 0, 1): 4 tebakan
(5, 4, 2, 3, 1, 0): 4 tebakan
(5, 4, 3, 0, 1, 2): 5 tebakan
(5, 4, 3, 0, 2, 1): 5 tebakan
(5, 4, 3, 1, 0, 2): 5 tebakan
(5, 4, 3, 1, 2, 0): 5 tebakan
(5, 4, 3, 2, 0, 1): 5 tebakan
(5, 4, 3, 2, 1, 0): 5 tebakan
Jumlah rata-rata: 4,41
Jumlah maksimal: 5
Jumlah minimum: 1
Jumlah yang berhasil: 720
Kode.
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)