I wrote an algorithm to solve the New York Times' Puzzle: Digits.
You could play the game here.
The rules of the game are as follow:
You were given 6 numbers and you could use simple operations (plus, minus, multiply, and divide) to reach a specific number given by the problem.
You DO NOT need to use all 6 number and you can not create negative and non-integer number.
Everything else is fair game.
The code I implemented uses Python to test all possibilities and compare it to the answer. I uses threading to branch out for all the possibilities and store the thread in a deque and only actually compute them when there's computing space.
Perhaps there's better way to do it?
Please help see if there's thing I could do to make it faster, easier to read, or more pythonic.
from itertools import combinations
import threading
import time
from collections import deque
class DigitSolver():
def __init__(self,starting_digits:list,target_digit:int):
"""
starting_digits is a list of integers that are given in the start position
target_digit is the target integer for the game.
"""
self.starting_digits = starting_digits
self.target_digit = target_digit
self.que = deque()
def __simplify__(self,l:list, target:int, step:list):
"""
l is a list of all integer at a postion of the game. the list could be of any length
step are the step must take to a position. the init state should be empty.
"""
def make_thread_n_que(base_list:list, new_num:int, target_num:int
, step:list, op:str,old_n1:int,old_n2:int):
t = threading.Thread(target=self.__simplify__, args = (base_list + [new_num],
target_num,
step + ["{}{}{}".format(old_n1,op,old_n2)]),
daemon=True)
self.que.append(t)
return
def pretty_print( step:list,op:str,n1:int,n2:int):
print("solution found:",*step + ["{}+{}".format(n1, n2)],sep="\n")
return
if len(l) == 1:
return
for index1, index2 in combinations(range(len(l)), 2):
templist = l[:]
# pop the number in the back first to prevent incorrect index.
if index1 > index2:
n1, n2 = templist.pop(index1), templist.pop(index2)
else:
n2, n1 = templist.pop(index2), templist.pop(index1)
# switch order so n1 > n2.
if n2 > n1:
n1, n2 = n2, n1
# case1 (addition)
add = n1 + n2
if add == target:
pretty_print(step,"+",n1,n2)
return
else:
make_thread_n_que(templist,add,target,step,"+",n1,n2)
# case 2 (multiplication)
multiply = n1 * n2
if multiply == target:
pretty_print(step,"×",n1,n2)
return
else:
make_thread_n_que(templist,multiply,target,step,"×",n1,n2)
# case 3 (subtraction)
subtract = n1 - n2
if subtract == target:
pretty_print(step,"−",n1,n2)
return
else:
make_thread_n_que(templist,subtract,target,step,"−",n1,n2)
# case 4 (full division) (ignore non full division)
divide, reminder = divmod(n1, n2) if n2 != 0 else (1,1)
if reminder:
continue
if divide == target:
pretty_print(step,"÷",n1,n2)
return
else:
make_thread_n_que(templist,divide,target,step,"÷",n1,n2)
def solve(self):
"""
call the function to start solving the puzzle
"""
self.__simplify__(self.starting_digits, self.target_digit, [])
while self.que:
if threading.active_count() < 900:
self.que.popleft().start()
else:
time.sleep(0.1)
if __name__ == "__main__":
# this is a demo of the code
# feel free to try with different numbers.
solver = DigitSolver([8,11,13,18,23,24],407)
solver.solve()
list
), thereby avoiding the scheduler tax that this code pays. \$\endgroup\$