5 Best Ways to Program to Check Whether We Can Form 24 by Placing Operators in Python

Rate this post

πŸ’‘ Problem Formulation: Imagine you have a set of four numbers and the challenge is to determine if it is possible to achieve a value of 24 by placing any combination of the basic arithmetic operators (addition, subtraction, multiplication, and division) between them. You must use each number exactly once. For example, given the input set [8, 1, 3, 2], the program should be able to verify if there’s a sequence of operations that results in 24.

Method 1: Brute Force Recursion

This method involves a recursive function that tries every possible combination of operations on the set of numbers. The function takes an array of numbers and recursively applies all possible operations between pairs of numbers until the array is reduced to a single value, checking for the target result of 24.

Here’s an example:

from itertools import permutations

def can_form_24(nums):
    if not nums:
        return False
    if len(nums) == 1:
        return abs(nums[0] - 24) < 1e-6
    for p in permutations(nums):
        for i in range(1, len(p)):
            for result in {p[0] + p[i], p[0] - p[i], p[0] * p[i], (p[0] / p[i]) if p[i] != 0 else 0}:
                if can_form_24([result] + list(p[1:i]) + list(p[i+1:])):
                    return True
    return False

print(can_form_24([8, 1, 3, 2]))

Output:

True

This recursive function, can_form_24, computes every possible permutation of the input list of numbers, then checks all possible outcomes of applying addition, subtraction, multiplication, and division to each paired numbers. If any recursive path returns True, indicating that the numbers can form 24, the function exits and returns True.

Method 2: Depth-First Search with Pruning

In the Depth-First Search (DFS) approach with pruning, the idea is to explore all possible combinations of numbers and operators using a depth-first search algorithm but with an optimization step that prunes branches that don’t lead to a solution, reducing the total number of computations drastically. Pruning can be done by stopping further computation on a branch as soon as the intermediate result is greater than 24 (assuming all numbers are positive) or through other smart heuristics.

Here’s an example:

def dfs(nums, target):
    if len(nums) == 1:
        return abs(nums[0] - target) < 1e-6
    for i in range(len(nums)):
        for j in range(len(nums)):
            if i != j:
                new_nums = [nums[k] for k in range(len(nums)) if k != i and k != j]
                results = [nums[i] + nums[j], nums[i] - nums[j], nums[i] * nums[j]]
                if nums[j] != 0: 
                    results.append(nums[i] / nums[j])
                for result in results:
                    if dfs(new_nums + [result], target):
                        return True
    return False

print(dfs([8, 1, 3, 2], 24))

Output:

True

This DFS method employs a function dfs that tries to form the target (24 in our case) using different operations on the numbers provided. Instead of calculating all permutations, it picks two numbers at each step and performs operations, then proceeds with a smaller set of numbers. It also has a base condition that checks if there’s only one number left and compares it with the target.

Method 3: Iterative Stack-based Solution

Rather than recursive approaches that can lead to call stack overhead, an iterative solution using a stack can be implemented. This method iterates through each permutation of numbers and simulates operator application with a stack, pushing and popping intermediate results as necessary until a single value is obtained to be checked against the desired result.

Here’s an example:

from itertools import permutations, product

def evaluate_stack(ops, nums):
    stack = []
    for num_or_op in ops:
        if num_or_op in '+-*/':
            b, a = stack.pop(), stack.pop()
            if num_or_op == '+': stack.append(a + b)
            elif num_or_op == '-': stack.append(a - b)
            elif num_or_op == '*': stack.append(a * b)
            elif b != 0: stack.append(a / b)
        else:
            stack.append(num_or_op)
    return stack[0]

def can_form_24_iterative(nums):
    for p in permutations(nums):
        for ops in product('+-*/', repeat=3):
            if abs(evaluate_stack(p + ops, nums) - 24) < 1e-6:
                return True
    return False

print(can_form_24_iterative([8, 1, 3, 2]))

Output:

True

This stack-based approach involves iterating through permutations of the numbers and all combinations of 3 operators. It applies these operators using an evaluation stack. The function evaluate_stack processes the stack and operations to calculate a result, which is then compared to the target of 24.

Method 4: Using an Arithmetic Expression Tree

The arithmetic expression tree method is a more structured way of exploring combinations. It involves constructing binary trees where leaf nodes represent numbers and internal nodes represent operators. Each unique tree corresponds to a unique arithmetic expression, and by generating all possible tree structures, we can check if any represent an expression that evaluates to 24.

Here’s an example:

# Example code structure for generating expression trees and evaluating them
# This is a conceptual example and would require significant additional detail to run

def generate_expression_trees(nums):
    # Code to generate all possible expression trees
    pass

def evaluate_expression_tree(tree):
    # Code to evaluate a given expression tree
    pass

# Generate all possible expression trees with nums and ops
# Evaluate each tree to check if it equals 24
# Return True if any tree evaluates to 24

Although the exact implementation details are extensive for an article example, the concept of using an arithmetic tree would involve generating all possible tree structures with the numbers as leaves and the arithmetic operators as internal nodes. Then, by evaluating each tree, the program can determine if the desired result can be achieved.

Bonus One-Liner Method 5: Using Eval with Caution

As a fun, albeit risky one-liner, Python’s eval() function can execute arithmetic expressions from strings. Below is a highly experimental one-liner that combines permutations of numbers with all operator combinations and uses eval to test each possibility. Note: using eval can pose security risks and must be done with extreme caution if the inputs are not completely controlled.

Here’s an example:

from itertools import permutations, product

print(any(abs(eval(''.join(str(x) for x in p + ops))) == 24 for p in permutations([8, 1, 3, 2]) for ops in product('+-*/', repeat=3)))

Output:

True

This one-liner uses permutations of the input numbers and all operator combinations, creating strings that represent expressions. It then evaluates each expression with eval() and checks if any results in 24. This should only be used for controlled, safe inputs and as a demonstration of Python’s dynamic execution capabilities.

Summary/Discussion

  • Method 1: Brute Force Recursion. It’s straightforward and exhaustive but may have performance issues for larger input sets due to the recursive nature and the number of computations required.
  • Method 2: Depth-First Search with Pruning. It’s an optimized version of brute force that can handle larger input sets more effectively by avoiding unnecessary computations, though it still can be improved.
  • Method 3: Iterative Stack-based Solution. This avoids recursion limitations and is suitable for systems with limited call stack memory, but it still has to iterate through many combinations.
  • Method 4: Arithmetic Expression Tree. It’s a structured approach that can be visually interpreted and debugged but requires complex tree construction and evaluation algorithms.
  • Method 5: Using Eval with Caution. It’s a quick one-liner for small, well-controlled problems, but it is not recommended for uncontrolled inputs due to security risks.