1#!/bin/env python323from collections import defaultdict4import sys5import itertools6from typing import Sequence789class Add:10 def __init__(self, one, two):11 self.one = one12 self.two = two13 self.res = self.one + self.two1415 def __str__(self):16 return f"{self.one} + {self.two} = {self.res}"1718 def __repr__(self):19 return self.__str__()2021 def operate(self):22 return self.res232425class Mul:26 def __init__(self, one, two):27 self.one = one28 self.two = two29 self.res = self.one * self.two3031 def __str__(self):32 return f"{self.one} * {self.two} = {self.res}"3334 def __repr__(self):35 return self.__str__()3637 def operate(self):38 return self.res394041class Sub:42 def __init__(self, one, two):43 self.one = one44 self.two = two45 if self.two > self.one:46 self.one, self.two = self.two, self.one4748 self.res = self.one - self.two4950 def __str__(self):51 return f"{self.one} - {self.two} = {self.res}"5253 def __repr__(self):54 return self.__str__()5556 def operate(self):57 return self.res585960class Div:61 def __init__(self, one, two):62 self.one = max(one, two)63 self.two = min(one, two)64 self.res = None6566 try:67 d = self.one / self.two68 if int(d) == d:69 self.res = int(d)70 except ZeroDivisionError:71 pass7273 def __str__(self):74 return f"{self.one} / {self.two} = {self.res}"7576 def __repr__(self):77 return self.__str__()787980ops = [Add, Sub, Mul, Div]81paths = []82unique = {}838485def solve(digits: Sequence[int], target: int, path: list):86 if target in digits:87 paths.append(path)8889 for combo in itertools.combinations(range(0, len(digits)), 2):90 for op in ops:91 o = op(digits[combo[0]], digits[combo[1]])92 res = o.res93 if res is None:94 continue9596 path_new = path.copy()97 path_new.append(o)98 digits_new = [d[1] for d in enumerate(digits) if d[0] not in combo]99 digits_new.append(res)100101 # We prefer shorter paths because that seems like the right102 # heuristic to use.103 if res not in unique or len(unique[res]) > len(path_new):104 unique[res] = path_new105106 solve(digits_new, target, path_new)107108109def main(argv: Sequence[str]) -> int:110 if len(argv) <= 2:111 print("usage: solver.py <DIGITS> <TARGET>")112 return 1113114 digits = [int(x) for x in argv[1:-1]]115 target = int(argv[-1])116 solve(digits, target, [])117118 for d in digits:119 global unique120 unique[d] = "given"121122 print("Correct paths")123 for path in paths:124 for op in path:125 print(op)126 print()127128 print(f"Total possible paths: {len(paths)}")129 print(f"Unique numbers encountered: {len(unique)}")130131 print("Ops used:")132 op_use_count = defaultdict(int)133 for path in paths:134 for op in path:135 op_use_count[op.__class__.__name__] += 1136 for op in ops:137 print(f"\t{op.__name__}\t{op_use_count[op.__name__]}")138 print()139140 print("Contiguous constructable numbers")141 for i in range(0, max(unique.keys())):142 if i in unique:143 print(f"{i}\t{unique[i]}")144 else:145 break146147 # A possible method of sorting, suggested by dad. Just count the number of148 # multiplications.149 # sorted_paths = sorted(paths, key=lambda path: max((r.res for r in path if isinstance(r, Mul))))150 # for path in sorted_paths:151 # for op in path:152 # print(op)153 # print()154155 return 0156157158if __name__ == "__main__":159 sys.exit(main(sys.argv))