Skip to content

Instantly share code, notes, and snippets.

@jakelevi1996
Last active August 11, 2022 22:37
Show Gist options
  • Save jakelevi1996/ecfb08f7747166927e6885a3e8cbddae to your computer and use it in GitHub Desktop.
Save jakelevi1996/ecfb08f7747166927e6885a3e8cbddae to your computer and use it in GitHub Desktop.
Solving "Tchisla" in Python (WIP)

Solving "Tchisla" in Python

Solve problems from the Tchisla app in Python. Code is in the tchisla.py script below. Parameters for the problem being solved can be modified in the if __name__ == "__main__" block at the bottom of tchisla.py.

TODO:

  • Avoid creating strings for expressions until the values have been validated
    • This could involve passing the operator and child expressions to Solver._validate instead of the resulting expression, calculting the value using the Operator.operation attribute or Operator.__call__ method in Solver._validate, and then only creating the resulting expression (including creating its string using the Operator.get_string attribute) if the value is valid
  • Change target, n and n_count_target to command line arguments
  • Add more comments
  • Save dictionaries to file so that they can be inspected/reused in future script calls
from math import sqrt
from time import perf_counter
class Expression:
def __init__(self, value, expression_str, n_count):
self.value = value
self.expression_str = expression_str
self.n_count = n_count
def __repr__(self):
return "%s = %s" % (self.value, self.expression_str)
class Operator:
def __init__(self, operation, str_func):
self.operation = operation
self.str_func = str_func
def __call__(self, *args):
try:
return Expression(
self.operation(*[float(a.value) for a in args]),
self.str_func(*[a.expression_str for a in args]),
sum(a.n_count for a in args),
)
except Exception as e:
return None
class Solver:
def __init__(self, n, max_intermediate_value=1e10):
# Initialise generic private attributes
self._n = n
self._max_intermediate_value = max_intermediate_value
self._next_n_length = 2
n_expression = Expression(n, str(n), 1)
self._value_dict = {n: n_expression}
self._new_value_list = [n_expression]
self._old_value_list = []
# Initialise factorial dictionary
self._factorial_dict = {1: 1}
f = 1
while self._factorial_dict[f] <= max_intermediate_value:
f += 1
self._factorial_dict[f] = f * self._factorial_dict[f - 1]
# Initialise lists of operations
self._commutative_op_list = [
Operator(lambda a, b: a + b, lambda a, b: "(%s) + (%s)" % (a, b)),
Operator(lambda a, b: a * b, lambda a, b: "(%s) * (%s)" % (a, b)),
]
self._non_commutative_op_list = [
Operator(lambda a, b: a - b, lambda a, b: "(%s) - (%s)" % (a, b)),
Operator(lambda a, b: a / b, lambda a, b: "(%s) / (%s)" % (a, b)),
Operator(lambda a, b: a ** b, lambda a, b: "(%s) ^ (%s)" % (a, b)),
]
self._unary_op_list = [
Operator(lambda a: sqrt(a), lambda a: "sqrt(%s)" % a),
Operator(lambda a: self._factorial_dict[a], lambda a: "(%s)!" % a),
]
def solve(self, target, target_n_count=10):
self._target = target
self._target_n_count = target_n_count
while True:
print(
"Step = %i, dictionary size = %i, with %i new expressions"
% (
self._next_n_length,
len(self._value_dict),
len(self._new_value_list),
),
end="",
)
self._temp_new_value_dict = dict()
# Add next multi-n string to appropriate iterables
multi_n_string = str(self._n) * self._next_n_length
multi_n_expression = Expression(
int(multi_n_string),
multi_n_string,
self._next_n_length,
)
self._validate(multi_n_expression)
self._next_n_length += 1
for op in self._unary_op_list:
print(".", end="", flush=True)
for e in self._new_value_list:
self._validate(op(e))
for op in self._commutative_op_list:
print(".", end="", flush=True)
for i, e in enumerate(self._new_value_list):
for j, e2 in enumerate(self._new_value_list):
if j >= i:
self._validate(op(e, e2))
for e2 in self._old_value_list:
self._validate(op(e, e2))
for op in self._non_commutative_op_list:
print(".", end="", flush=True)
for e in self._new_value_list:
for e2 in self._new_value_list:
self._validate(op(e, e2))
for e2 in self._old_value_list:
self._validate(op(e, e2))
self._validate(op(e2, e))
print()
self._old_value_list += self._new_value_list
self._new_value_list = list(self._temp_new_value_dict.values())
if len(self._new_value_list) == 0:
return None
if target in self._value_dict:
if self._value_dict[target].n_count <= target_n_count:
return self._value_dict[target].n_count
def _validate(self, expression):
if expression is None:
return
value = expression.value
is_valid = (
not isinstance(value, complex)
and (value > 0)
and (value <= self._max_intermediate_value)
and (value == int(value)) # Sometimes remove this, EG for 64 # 2
and (expression.n_count <= self._target_n_count)
)
if is_valid:
value = int(value)
if value in self._value_dict:
previous_n_count = self._value_dict[value].n_count
if expression.n_count >= previous_n_count:
return
self._value_dict[value] = expression
self._temp_new_value_dict[value] = expression
if value == self._target:
print(
"\nFound target with %i \"n\"s! %s"
% (expression.n_count, expression)
)
if __name__ == "__main__":
t_start = perf_counter()
Solver(n=2, max_intermediate_value=1e6).solve(target=64, target_n_count=3)
print("Script finished in %.3f secs" % (perf_counter() - t_start))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment