Python 2 (работает быстрее, если используется Pypy)
Считается, что почти всегда угадывают правильное соединение в 10 раундов или ниже
Мой алгоритм взят из моего ответа для вдохновителя как мое хобби (см. В Ideone ). Идея состоит в том, чтобы найти предположение, которое минимизирует количество оставшихся возможностей в худшем случае. Мой алгоритм ниже просто перебор, но чтобы сэкономить время, он просто выбирает случайное предположение, если количество оставшихся возможностей больше, чем RANDOM_THRESHOLD
. Вы можете поиграть с этим параметром, чтобы ускорить процесс или увидеть лучшую производительность.
Алгоритм довольно медленный, в среднем 10 с за один прогон, если он выполняется с использованием Pypy (если используется обычный интерпретатор CPython, это около 30 с), поэтому я не могу проверить его на всех перестановках. Но производительность довольно хорошая, после 30 тестов я не видел ни одного случая, когда бы он не мог найти правильное соединение в 10 раундах или ниже.
Во всяком случае, если это используется в реальной жизни шоу, у него есть много времени до следующего раунда (одна неделя?), Поэтому этот алгоритм может быть использован в реальной жизни = D
Поэтому я думаю, можно с уверенностью предположить, что в среднем это найдет правильные пары в 10 догадках или ниже.
Попробуй сам. Я мог бы улучшить скорость в следующие несколько дней (РЕДАКТИРОВАТЬ: дальнейшее улучшение кажется трудным, поэтому я просто оставлю код как есть. Я пробовал делать только случайный выбор, но даже при size=7
трех неудачных попытках из 5040 , поэтому я решил сохранить более умный метод). Вы можете запустить его как:
pypy are_you_the_one.py 10
Или, если вы просто хотите посмотреть, как это работает, введите меньшее число (чтобы оно работало быстрее)
Чтобы запустить полный тест (предупреждение: это займет очень много времени size
> 7), поставьте отрицательное число.
Полный тест для size=7
(завершено за 2 м 32 с):
...
(6, 5, 4, 1, 3, 2, 0): 5 догадок
(6, 5, 4, 2, 0, 1, 3): 5 догадок
(6, 5, 4, 2, 0, 3, 1): 4 догадки
(6, 5, 4, 2, 1, 0, 3): 5 догадок
(6, 5, 4, 2, 1, 3, 0): 6 догадок
(6, 5, 4, 2, 3, 0, 1): 6 догадок
(6, 5, 4, 2, 3, 1, 0): 6 догадок
(6, 5, 4, 3, 0, 1, 2): 6 догадок
(6, 5, 4, 3, 0, 2, 1): 3 догадки
(6, 5, 4, 3, 1, 0, 2): 7 догадок
(6, 5, 4, 3, 1, 2, 0): 7 догадок
(6, 5, 4, 3, 2, 0, 1): 4 догадки
(6, 5, 4, 3, 2, 1, 0): 7 догадок
Средний счет: 5,05
Максимальное количество: 7
Минимальное количество: 1
Num success: 5040
Если RANDOM_THRESHOLD
и CLEVER_THRESHOLD
для обоих задано очень высокое значение (например, 50000), это заставит алгоритм найти оптимальное предположение, которое минимизирует количество возможностей в худшем случае. Это очень медленно, но очень мощно. Например, его запуск size=6
подтверждает, что он может найти правильные пары максимум за 5 раундов.
Хотя среднее значение выше по сравнению с использованием аппроксимации (которая в среднем составляет 4,11 раунда), но оно всегда получается успешным, даже больше, когда остается один запасной раунд. Это еще больше укрепляет нашу гипотезу о том, что, когда size=10
, он почти всегда должен найти правильные пары в 10 раундов или меньше.
Результат (выполнено за 3м 9с):
(5, 4, 2, 1, 0, 3): 5 догадок
(5, 4, 2, 1, 3, 0): 5 догадок
(5, 4, 2, 3, 0, 1): 4 догадки
(5, 4, 2, 3, 1, 0): 4 догадки
(5, 4, 3, 0, 1, 2): 5 догадок
(5, 4, 3, 0, 2, 1): 5 догадок
(5, 4, 3, 1, 0, 2): 5 догадок
(5, 4, 3, 1, 2, 0): 5 догадок
(5, 4, 3, 2, 0, 1): 5 догадок
(5, 4, 3, 2, 1, 0): 5 догадок
Средний счет: 4.41
Максимальное количество: 5
Минимальное количество: 1
Num success: 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)