Python 2 (corre más rápido si corres usando Pypy)
Se cree que casi siempre adivina el emparejamiento correcto en 10 rondas o menos
Mi algoritmo se toma de mi respuesta para mastermind como mi hobby (ver en Ideone ). La idea es encontrar la suposición que minimice el número de posibilidades que quedan en el peor de los casos. Mi algoritmo a continuación solo lo fuerza por fuerza bruta, pero para ahorrar tiempo, solo selecciona conjeturas aleatorias si el número de posibilidades restantes es mayor que RANDOM_THRESHOLD
. Puede jugar con este parámetro para acelerar las cosas o para ver un mejor rendimiento.
El algoritmo es bastante lento, en promedio 10 segundos para una ejecución si se ejecuta utilizando Pypy (si se utiliza el intérprete CPython normal, son alrededor de 30 segundos), por lo que no puedo probarlo en todas las permutaciones. Pero el rendimiento es bastante bueno, después de alrededor de 30 pruebas, no he visto ninguna instancia en la que no pueda encontrar el emparejamiento correcto en 10 rondas o menos.
De todos modos, si esto se usa en un espectáculo de la vida real, tiene mucho tiempo antes de la próxima ronda (¿una semana?), Por lo que este algoritmo se puede usar en la vida real = D
Así que creo que es seguro asumir que, en promedio, esto encontrará los emparejamientos correctos en 10 conjeturas o menos.
Inténtalo tú mismo. Podría mejorar la velocidad en los próximos días (EDITAR: parece difícil mejorar aún más, así que dejaré el código tal como está. Intenté hacer una selección aleatoria, pero incluso size=7
en 3 de los 5040 casos falla , así que decidí mantener el método más inteligente). Puedes ejecutarlo como:
pypy are_you_the_one.py 10
O, si solo quiere ver cómo funciona, ingrese un número menor (para que se ejecute más rápido)
Para ejecutar una prueba completa (advertencia: tomará mucho tiempo size
> 7), ingrese un número negativo.
Prueba completa para size=7
(completado en 2m 32s):
...
(6, 5, 4, 1, 3, 2, 0): 5 conjeturas
(6, 5, 4, 2, 0, 1, 3): 5 conjeturas
(6, 5, 4, 2, 0, 3, 1): 4 conjeturas
(6, 5, 4, 2, 1, 0, 3): 5 conjeturas
(6, 5, 4, 2, 1, 3, 0): 6 conjeturas
(6, 5, 4, 2, 3, 0, 1): 6 conjeturas
(6, 5, 4, 2, 3, 1, 0): 6 conjeturas
(6, 5, 4, 3, 0, 1, 2): 6 conjeturas
(6, 5, 4, 3, 0, 2, 1): 3 conjeturas
(6, 5, 4, 3, 1, 0, 2): 7 conjeturas
(6, 5, 4, 3, 1, 2, 0): 7 conjeturas
(6, 5, 4, 3, 2, 0, 1): 4 conjeturas
(6, 5, 4, 3, 2, 1, 0): 7 conjeturas
Conteo promedio: 5.05
Cuenta máxima: 7
Cuenta mínima: 1
Num éxito: 5040
Si RANDOM_THRESHOLD
y CLEVER_THRESHOLD
se establecen en un valor muy alto (como 50000), que va a forzar el algoritmo para encontrar la estimación óptima que minimiza el número de posibilidades en el peor de los casos. Esto es muy lento, pero muy poderoso. Por ejemplo, ejecutarlo size=6
afirma que puede encontrar los emparejamientos correctos en un máximo de 5 rondas.
Aunque el promedio es más alto en comparación con el uso de la aproximación (que es 4.11 rondas en promedio), pero siempre tiene éxito, aún más con una ronda de sobra. Esto fortalece aún más nuestra hipótesis de que cuando size=10
, casi siempre debería encontrar los emparejamientos correctos en 10 rondas o menos.
El resultado (completado en 3m 9s):
(5, 4, 2, 1, 0, 3): 5 conjeturas
(5, 4, 2, 1, 3, 0): 5 conjeturas
(5, 4, 2, 3, 0, 1): 4 conjeturas
(5, 4, 2, 3, 1, 0): 4 conjeturas
(5, 4, 3, 0, 1, 2): 5 conjeturas
(5, 4, 3, 0, 2, 1): 5 conjeturas
(5, 4, 3, 1, 0, 2): 5 conjeturas
(5, 4, 3, 1, 2, 0): 5 conjeturas
(5, 4, 3, 2, 0, 1): 5 conjeturas
(5, 4, 3, 2, 1, 0): 5 conjeturas
Conteo promedio: 4.41
Cuenta máxima: 5
Cuenta mínima: 1
Num éxito: 720
El 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)