Python 2 (execute mais rápido se for executado usando Pypy)
Acredita-se que quase sempre adivinhe o emparelhamento correto em 10 rodadas ou menos
Meu algoritmo é retirado da minha resposta para o cérebro como meu hobby (veja em Ideone ). A idéia é encontrar um palpite que minimize o número de possibilidades restantes no pior caso. Meu algoritmo abaixo apenas a força bruta, mas para economizar tempo, basta escolher palpites aleatórios se o número de possibilidades restantes for maior que RANDOM_THRESHOLD
. Você pode brincar com esse parâmetro para acelerar as coisas ou obter um melhor desempenho.
O algoritmo é bastante lento, em média 10s para uma execução, se executado com Pypy (se o interpretador CPython normal estiver em torno de 30s), então não posso testá-lo em todas as permutações. Mas o desempenho é muito bom, depois de cerca de 30 testes, não vi nenhum caso em que ele não encontre o emparelhamento correto em 10 rodadas ou menos.
De qualquer forma, se isso for usado no programa da vida real, ele terá bastante tempo antes da próxima rodada (uma semana?) Para que esse algoritmo possa ser usado na vida real = D
Portanto, acho que é seguro supor que, em média, este ache os pares corretos em 10 palpites ou menos.
Tente você mesmo. Talvez eu melhore a velocidade nos próximos dias (EDIT: parece difícil melhorar ainda mais, então deixarei o código como está. Tentei apenas fazer uma escolha aleatória, mas mesmo assim size=7
, ele falha em 3 dos 5040 casos , então eu decidi manter o método mais inteligente). Você pode executá-lo como:
pypy are_you_the_one.py 10
Ou, se você quiser apenas ver como funciona, insira um número menor (para que seja mais rápido)
Para executar um teste completo (aviso: levará muito tempo para size
> 7), coloque um número negativo.
Teste completo para size=7
(concluído em 2m 32s):
...
(6, 5, 4, 1, 3, 2, 0): 5 palpites
(6, 5, 4, 2, 0, 1, 3): 5 palpites
(6, 5, 4, 2, 0, 3, 1): 4 palpites
(6, 5, 4, 2, 1, 0, 3): 5 palpites
(6, 5, 4, 2, 1, 3, 0): 6 palpites
(6, 5, 4, 2, 3, 0, 1): 6 palpites
(6, 5, 4, 2, 3, 1, 0): 6 palpites
(6, 5, 4, 3, 0, 1, 2): 6 palpites
(6, 5, 4, 3, 0, 2, 1): 3 palpites
(6, 5, 4, 3, 1, 0, 2): 7 palpites
(6, 5, 4, 3, 1, 2, 0): 7 palpites
(6, 5, 4, 3, 2, 0, 1): 4 palpites
(6, 5, 4, 3, 2, 1, 0): 7 palpites
Contagem média: 5.05
Contagem máxima: 7
Contagem mínima: 1
Num sucesso: 5040
Se RANDOM_THRESHOLD
e CLEVER_THRESHOLD
estiverem ambos definidos com um valor muito alto (como 50000), forçará o algoritmo a encontrar a estimativa ideal que minimiza o número de possibilidades no pior caso. Isso é muito lento, mas muito poderoso. Por exemplo, executá-lo size=6
afirma que ele pode encontrar os pares corretos em no máximo 5 rodadas.
Embora a média seja mais alta em comparação ao uso da aproximação (que é média de 4,11 rodadas), mas sempre é bem-sucedida, ainda mais com uma rodada sobrando. Isso reforça ainda mais nossa hipótese de que size=10
, quando , quase sempre deve encontrar os pares corretos em 10 rodadas ou menos.
O resultado (concluído em 3m 9s):
(5, 4, 2, 1, 0, 3): 5 palpites
(5, 4, 2, 1, 3, 0): 5 palpites
(5, 4, 2, 3, 0, 1): 4 palpites
(5, 4, 2, 3, 1, 0): 4 palpites
(5, 4, 3, 0, 1, 2): 5 palpites
(5, 4, 3, 0, 2, 1): 5 palpites
(5, 4, 3, 1, 0, 2): 5 palpites
(5, 4, 3, 1, 2, 0): 5 palpites
(5, 4, 3, 2, 0, 1): 5 palpites
(5, 4, 3, 2, 1, 0): 5 palpites
Contagem média: 4.41
Contagem máxima: 5
Contagem mínima: 1
Num sucesso: 720
O código.
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)