Project Euler Problem 93
By using each of the digits from the set, 1, 2, 3, 4, exactly once, and making use of the four arithmetic operations (+,
Solution
Answer: 1258
We must search over all 4-digit sets ${a,b,c,d} \subset {0,\dots,9}$, and for each set determine which positive integers can be formed using:
- each digit exactly once,
- the operations $+,-,\times,/$,
- arbitrary parenthesization.
The goal is to maximize the length of the consecutive run
$$1,2,3,\dots,n$$
starting from $1$.
Mathematical analysis
Suppose we pick four distinct digits $a,b,c,d$.
To build expressions:
- Arrange the digits in any order.
- Insert three operations chosen from:
$$+,-,\times,/$$ 3. Parenthesize in every valid binary-tree form.
Step 1 — Permutations of digits
There are
$$4! = 24$$
ways to order the digits.
Example with $1,2,3,4$:
$$(1,2,3,4), (1,2,4,3), \dots$$
Step 2 — Choices of operators
Each of the three operator slots has 4 choices, so:
$$4^3 = 64$$
operator combinations.
Example:
$$(+,\times,-)$$
corresponds to something like
$$a + b \times c - d$$
before parenthesization.
Step 3 — Parenthesizations
With four operands there are exactly five binary parenthesization structures:
$$\begin{aligned} 1.;& ((a;b);c);d \ 2.;& (a;(b;c));d \ 3.;& (a;b);(c;d) \ 4.;& a;((b;c);d) \ 5.;& a;(b;(c;d)) \end{aligned}$$
These correspond to all full binary trees on four leaves (Catalan number $C_3=5$).
Step 4 — Exact arithmetic
Division creates fractions, so floating-point arithmetic is dangerous due to rounding errors.
Instead, use exact rational arithmetic via Python’s Fraction.
Example:
$$\frac{1}{3} + \frac{1}{6} = \frac12$$
is represented exactly.
Step 5 — Collect positive integers
For every generated value:
- denominator must equal $1$,
- value must be positive.
Store all reachable integers in a set.
Then compute the longest consecutive prefix beginning at $1$.
Example:
If reachable integers are
$${1,2,3,4,5,6,7,8,9,10,12}$$
then the consecutive run length is $10$.
Python implementation
from itertools import combinations, permutations, product
from fractions import Fraction
# Apply an arithmetic operation safely
def apply_op(x, y, op):
if op == '+':
return x + y
if op == '-':
return x - y
if op == '*':
return x * y
if op == '/':
if y == 0:
return None
return x / y
# Evaluate all 5 parenthesization patterns
def generate_values(nums, ops):
a, b, c, d = map(Fraction, nums)
op1, op2, op3 = ops
results = []
# 1. ((a b) c) d
r1 = apply_op(a, b, op1)
if r1 is not None:
r2 = apply_op(r1, c, op2)
if r2 is not None:
r3 = apply_op(r2, d, op3)
if r3 is not None:
results.append(r3)
# 2. (a (b c)) d
r1 = apply_op(b, c, op2)
if r1 is not None:
r2 = apply_op(a, r1, op1)
if r2 is not None:
r3 = apply_op(r2, d, op3)
if r3 is not None:
results.append(r3)
# 3. (a b) (c d)
r1 = apply_op(a, b, op1)
r2 = apply_op(c, d, op3)
if r1 is not None and r2 is not None:
r3 = apply_op(r1, r2, op2)
if r3 is not None:
results.append(r3)
# 4. a ((b c) d)
r1 = apply_op(b, c, op2)
if r1 is not None:
r2 = apply_op(r1, d, op3)
if r2 is not None:
r3 = apply_op(a, r2, op1)
if r3 is not None:
results.append(r3)
# 5. a (b (c d))
r1 = apply_op(c, d, op3)
if r1 is not None:
r2 = apply_op(b, r1, op2)
if r2 is not None:
r3 = apply_op(a, r2, op1)
if r3 is not None:
results.append(r3)
return results
best_run = 0
best_digits = None
ops_list = ['+', '-', '*', '/']
# Try every 4-digit set
for digits in combinations(range(10), 4):
reachable = set()
# All orderings of the digits
for nums in permutations(digits):
# All operator choices
for ops in product(ops_list, repeat=3):
# Evaluate all expression forms
for val in generate_values(nums, ops):
# Keep positive integers only
if val.denominator == 1 and val > 0:
reachable.add(int(val))
# Find longest consecutive run from 1
n = 1
while n in reachable:
n += 1
run_length = n - 1
if run_length > best_run:
best_run = run_length
best_digits = digits
# Convert tuple to required string
answer = ''.join(map(str, best_digits))
print(answer)
Code walkthrough
Imports
from itertools import combinations, permutations, product
from fractions import Fraction
combinationsgenerates digit sets.permutationstries all digit orders.productgenerates operator triples.Fractionensures exact arithmetic.
Operation function
def apply_op(x, y, op):
Applies one arithmetic operation.
Important detail:
if op == '/' and y == 0:
return None
prevents division by zero.
Expression generation
generate_values(nums, ops)
Evaluates all 5 valid parenthesization patterns.
Example pattern:
((a b) c) d
means:
r1 = apply_op(a, b, op1)
r2 = apply_op(r1, c, op2)
r3 = apply_op(r2, d, op3)
Each successful evaluation is appended to results.
Main search loop
for digits in combinations(range(10), 4):
tries every 4-digit subset.
There are:
$$\binom{10}{4} = 210$$
such sets.
Generating all expressions
For each digit set:
for nums in permutations(digits):
tries all 24 orderings.
Then:
for ops in product(ops_list, repeat=3):
tries all 64 operator choices.
Then evaluates all 5 parenthesizations.
Total expressions per set:
$$24 \times 64 \times 5 = 7680$$
Very manageable computationally.
Recording integers
if val.denominator == 1 and val > 0:
keeps only positive integers.
Consecutive run detection
n = 1
while n in reachable:
n += 1
finds the first missing positive integer.
Thus the run length is:
n - 1
Verification with the example
For the set:
$${1,2,3,4}$$
the program finds:
- all integers from $1$ through $28$,
- but not $29$,
so the run length is $28$, matching the problem statement.
Final verification
The known optimal set should:
- use four distinct digits,
- produce a surprisingly long consecutive sequence,
- be returned in increasing order as a 4-digit string.
The exhaustive search guarantees correctness because:
- every digit ordering is tested,
- every operator combination is tested,
- every valid parenthesization is tested,
- arithmetic is exact.
The optimal digit set is:
$${1,2,5,8}$$
which produces consecutive integers from $1$ to $51$.
Therefore the required string is:
$$1258$$
Answer: 1258