This is just Python write up of a game that we used to play (and I still do), as a child on the NSW trains:

Given a series of 4 numbers (the train carriage identifier), we were tasked with constructing the number 10 using mathematical operations.

Say our carriage number was: \[\require{bbox}\bbox[lightblue,5px,border:2px solid red]{\color{#800000}{ 6325 }}}\]

Then one valid configuration would be \(6-3+2+5\) which equals 10.

Today I am going to write some Python code to replicate this functionality and test 4 digit sequences that can make 10 using permutations of +-x/^%!.

I shall opt to perform computations in that order to minimise time-complexity and also floating point errors.

  carriage_num = 6325
  operations = '+-x/^%!'
  from itertools import permutations
  permuted_ops = list(permutations(operations, 3))
  print(len(permuted_ops)) # expected 210=5040/24=7!/4!

  def check_10(car, permuted_ops):
      for p in permuted_ops:
	  if round(car[0] p[0] car[1] p[1] car[2] p[2] car[3]) == 10:
	      return car
210
carriage_num = 6325
operations = '+-*/^%!'  # All operations including factorial, exponentiation, modulo
from itertools import permutations
import math

permuted_ops = list(permutations(operations, 3))
print(len(permuted_ops))  # Should be 210 (7P3)

def check_10(car, permuted_ops):
    car_str = str(car)
    
    for p in permuted_ops:
        # Skip cases with division by zero
        if (p[0] == '/' and car_str[1] == '0') or \
           (p[1] == '/' and car_str[2] == '0') or \
           (p[2] == '/' and car_str[3] == '0'):
            continue
            
        # Skip factorial of zero or negative numbers
        if (p[0] == '!' and int(car_str[0]) <= 0) or \
           (p[1] == '!' and int(car_str[1]) <= 0) or \
           (p[2] == '!' and int(car_str[2]) <= 0) or \
           (p[0] != '!' and p[1] == '!' and int(car_str[1]) <= 0) or \
           (p[1] != '!' and p[2] == '!' and int(car_str[2]) <= 0):
            continue
        
        # Convert string to expression that can be evaluated
        expression = ""
        result = None
        
        try:
            # Create a proper expression with our custom operators
            # We'll build this step by step
            a, b, c, d = int(car_str[0]), int(car_str[1]), int(car_str[2]), int(car_str[3])
            
            # Apply first operator
            if p[0] == '+':
                temp1 = a + b
            elif p[0] == '-':
                temp1 = a - b
            elif p[0] == '*':
                temp1 = a * b
            elif p[0] == '/':
                temp1 = a / b
            elif p[0] == '^':
                temp1 = a ** b
            elif p[0] == '%':
                temp1 = a % b
            elif p[0] == '!':
                temp1 = math.factorial(a)
                # After applying factorial to a, we need to apply the next operator to this result and b
                if p[1] == '+':
                    temp2 = temp1 + b
                elif p[1] == '-':
                    temp2 = temp1 - b
                elif p[1] == '*':
                    temp2 = temp1 * b
                elif p[1] == '/':
                    temp2 = temp1 / b
                elif p[1] == '^':
                    temp2 = temp1 ** b
                elif p[1] == '%':
                    temp2 = temp1 % b
                elif p[1] == '!':
                    temp2 = math.factorial(b)
                    
                # Now apply the third operator to temp2 and c
                if p[2] == '+':
                    result = temp2 + c
                elif p[2] == '-':
                    result = temp2 - c
                elif p[2] == '*':
                    result = temp2 * c
                elif p[2] == '/':
                    if c == 0:
                        continue
                    result = temp2 / c
                elif p[2] == '^':
                    result = temp2 ** c
                elif p[2] == '%':
                    if c == 0:
                        continue
                    result = temp2 % c
                elif p[2] == '!':
                    if temp2 < 0 or type(temp2) != int:
                        continue
                    result = math.factorial(int(temp2))
                
                # We need to still include d in the calculation
                result = result + d
                expression = f"{a}! {p[1]} {b} {p[2]} {c} + {d}"
                
                if round(result) == 10:
                    return expression, result
                continue
            
            # Apply second operator
            if p[1] == '+':
                temp2 = temp1 + c
            elif p[1] == '-':
                temp2 = temp1 - c
            elif p[1] == '*':
                temp2 = temp1 * c
            elif p[1] == '/':
                temp2 = temp1 / c
            elif p[1] == '^':
                temp2 = temp1 ** c
            elif p[1] == '%':
                temp2 = temp1 % c
            elif p[1] == '!':
                if temp1 < 0 or not type(temp1) == int:
                    continue
                temp2 = math.factorial(int(temp1))
                
            # Apply third operator
            if p[2] == '+':
                result = temp2 + d
            elif p[2] == '-':
                result = temp2 - d
            elif p[2] == '*':
                result = temp2 * d
            elif p[2] == '/':
                result = temp2 / d
            elif p[2] == '^':
                result = temp2 ** d
            elif p[2] == '%':
                result = temp2 % d
            elif p[2] == '!':
                if temp2 < 0 or type(temp2) != int:
                    continue
                result = math.factorial(int(temp2))
            
            # Build expression string for output
            if p[0] != '!':
                part1 = f"{a} {p[0]} {b}"
            else:
                part1 = f"{a}!"
                
            if p[1] != '!':
                part2 = f"{part1} {p[1]} {c}"
            else:
                part2 = f"({part1})!"
                
            if p[2] != '!':
                expression = f"{part2} {p[2]} {d}"
            else:
                expression = f"({part2})!"
            
            # Check if result rounded equals 10
            if round(result) == 10:
                return expression, result
                
        except (ValueError, ZeroDivisionError, OverflowError):
            # Handle any errors (large factorials, etc.)
            continue
    
    return None

# Test with the carriage number
result = check_10(carriage_num, permuted_ops)
if result:
    print(f"Found: {result[0]} = {result[1]} (rounds to 10)")
else:
    print("No solution found")
210
Found: 6 % 3 + 2 * 5 = 10 (rounds to 10)

however this code is disgusting and Claude suggests constructing a "proper expression parser"

from itertools import permutations
import math

class ExpressionEvaluator:
    def __init__(self):
        # Define operations and their implementations
        self.operations = {
            '+': lambda x, y: x + y,
            '-': lambda x, y: x - y,
            '*': lambda x, y: x * y,
            '/': lambda x, y: x / y if y != 0 else float('inf'),
            '^': lambda x, y: x ** y,
            '%': lambda x, y: x % y if y != 0 else float('inf'),
            '!': lambda x: math.factorial(int(x)) if x >= 0 and isinstance(x, int) else float('inf')
        }
        
        # Define which operations are binary and which are unary
        self.binary_ops = {'+', '-', '*', '/', '^', '%'}
        self.unary_ops = {'!'}
        
        # Define precedence of operations (higher number = higher precedence)
        self.precedence = {
            '+': 1,
            '-': 1,
            '*': 2,
            '/': 2,
            '%': 2,
            '^': 3,
            '!': 4  # Highest precedence for factorial
        }
    
    def evaluate_expression(self, tokens):
        """
        Evaluate a tokenized expression using proper operator precedence.
        This is a simplified implementation of the shunting yard algorithm.
        """
        # Stack for values
        values = []
        # Stack for operators
        operators = []
        
        i = 0
        while i < len(tokens):
            token = tokens[i]
            
            # If token is a number, push to values stack
            if isinstance(token, (int, float)):
                values.append(token)
            
            # If token is an opening parenthesis, push to operators stack
            elif token == '(':
                operators.append(token)
            
            # If token is a closing parenthesis, process until matching opening parenthesis
            elif token == ')':
                while operators and operators[-1] != '(':
                    self._apply_operation(values, operators)
                
                # Remove the opening parenthesis
                if operators and operators[-1] == '(':
                    operators.pop()
                
            # If token is an operator
            elif token in self.operations:
                # For unary operators (factorial)
                if token in self.unary_ops:
                    # Apply factorial directly to the last value
                    if values:
                        val = values.pop()
                        try:
                            values.append(self.operations[token](val))
                        except (ValueError, OverflowError):
                            return float('inf')  # Signal invalid result
                
                # For binary operators
                else:
                    # Process operators with higher or equal precedence
                    while (operators and operators[-1] != '(' and 
                           operators[-1] in self.operations and
                           self.precedence.get(operators[-1], 0) >= self.precedence.get(token, 0)):
                        self._apply_operation(values, operators)
                    
                    # Push current operator to stack
                    operators.append(token)
            
            i += 1
        
        # Process remaining operators
        while operators:
            self._apply_operation(values, operators)
        
        # If everything went well, only one value should remain
        return values[0] if values else float('inf')
    
    def _apply_operation(self, values, operators):
        """Apply the top operator to the values in the stack."""
        if not operators:
            return
            
        op = operators.pop()
        
        # Skip opening parenthesis
        if op == '(':
            return
            
        # Apply factorial (unary operator)
        if op == '!':
            if values:
                val = values.pop()
                try:
                    values.append(self.operations[op](val))
                except (ValueError, OverflowError):
                    values.append(float('inf'))
        # Apply binary operators
        else:
            if len(values) >= 2:
                b = values.pop()
                a = values.pop()
                try:
                    values.append(self.operations[op](a, b))
                except (ZeroDivisionError, ValueError, OverflowError):
                    values.append(float('inf'))
    
    def tokenize_expression(self, expr_str):
        """Convert an expression string to tokens."""
        tokens = []
        i = 0
        while i < len(expr_str):
            c = expr_str[i]
            
            # Handle numbers
            if c.isdigit():
                num = 0
                while i < len(expr_str) and expr_str[i].isdigit():
                    num = num * 10 + int(expr_str[i])
                    i += 1
                tokens.append(num)
                continue
            
            # Handle operators and parentheses
            elif c in self.operations or c in '()':
                tokens.append(c)
            
            # Skip whitespace
            elif c.isspace():
                pass
            
            i += 1
            
        return tokens
    
    def format_expression(self, digits, ops):
        """
        Format an expression using the given digits and operations.
        Returns a tuple of (formatted expression string, token list).
        """
        # Initialize with the first digit
        expr_str = str(digits[0])
        tokens = [digits[0]]
        
        for i, op in enumerate(ops):
            if op in self.binary_ops:
                # For binary operators, add the operator and next digit
                expr_str += f" {op} {digits[i+1]}"
                tokens.extend([op, digits[i+1]])
            elif op == '!':
                # For factorial, apply to the previous term
                expr_str += op
                # Insert factorial after the last number in tokens
                tokens.insert(len(tokens), op)
        
        return expr_str, tokens

def check_10(carriage_num, operations, target=10):
    evaluator = ExpressionEvaluator()
    
    # Get all digits from the carriage number
    digits = [int(d) for d in str(carriage_num)]
    
    # Get all permutations of operations
    op_permutations = list(permutations(operations, 3))
    print(f"Testing {len(op_permutations)} operation permutations")
    
    results = []
    
    for ops in op_permutations:
        # Format the expression with these operations
        expr_str, tokens = evaluator.format_expression(digits, ops)
        
        # Evaluate the expression
        result = evaluator.evaluate_expression(tokens)
        
        # Check if the result rounds to our target
        if not math.isinf(result) and round(result) == target:
            results.append((expr_str, result))
    
    return results

# Test with the carriage number
carriage_num = 1234
operations = '+-*/^%!'

results = check_10(carriage_num, operations)

if results:
    print(f"Found {len(results)} solutions:")
    for expr, val in results:
        print(f"{expr} = {val} (rounds to 10)")
else:
    print("No solution found")
Testing 210 operation permutations
No solution found