Project Euler Problem 222
What is the length of the shortest pipe, of internal radius pu{50 mm}, that can fully contain 21 balls of radii pu{30 mm
Solution
Answer: 1590933
Let the pipe have internal radius
$$R=50\text{ mm}.$$
We must place the $21$ balls of radii
$$30,31,\dots,50 \text{ mm}$$
inside the cylinder so that the total pipe length is minimal.
1. Geometric analysis
Consider two consecutive balls with radii $r$ and $s$.
In the optimal packing:
- each ball touches the cylinder wall,
- consecutive balls touch each other,
- their centers alternate from one side of the cylinder to the other.
The cross-sectional picture gives:
- distance from the cylinder axis to the center of a ball of radius $r$:
$$R-r$$
- transverse separation between the two centers:
$$(R-r)+(R-s)=2R-r-s.$$
Since the balls touch each other, the center distance is
$$r+s.$$
Therefore, if $d(r,s)$ is the axial separation between the centers,
$$d(r,s)^2 + (2R-r-s)^2 = (r+s)^2.$$
Hence
$$d(r,s) = \sqrt{(r+s)^2-(2R-r-s)^2}.$$
With $R=50$,
$$d(r,s) = \sqrt{(r+s)^2-(100-r-s)^2}.$$
Factor:
$$d(r,s) = \sqrt{100(2r+2s-100)} = 10\sqrt{2(r+s)-100}.$$
So the distance depends only on $r+s$.
Total pipe length
If the first and last balls have radii $a$ and $b$, then the pipe length is
$$L = a+b+\sum d(r_i,r_{i+1}).$$
Thus we must minimize the sum of the adjacent separations.
Because $d(r,s)$ is increasing in $r+s$, we want adjacent sums as small as possible.
The optimal ordering is:
$$50,48,46,\dots,32,30,31,33,35,\dots,47,49.$$
That is:
- decreasing evens,
- then increasing odds.
This keeps neighboring sums very small throughout the chain.
2. Python implementation
from math import sqrt
# Ball radii
balls = list(range(30, 51))
# Optimal ordering
order = (
list(range(50, 29, -2)) + # 50,48,...,30
list(range(31, 50, 2)) # 31,33,...,49
)
def axial_distance(r, s):
"""
Axial separation between centers of two touching balls
inside a cylinder of radius 50 mm.
"""
return sqrt((r + s)**2 - (100 - r - s)**2)
# Total pipe length in mm
length_mm = order[0] + order[-1]
for i in range(len(order) - 1):
length_mm += axial_distance(order[i], order[i + 1])
# Convert mm -> micrometres
length_um = round(length_mm * 1000)
print(length_mm)
print(length_um)
3. Code walkthrough
Constructing the ordering
order = (
list(range(50, 29, -2)) +
list(range(31, 50, 2))
)
creates
$$[50,48,46,\dots,30,31,33,\dots,49].$$
Axial distance formula
sqrt((r + s)**2 - (100 - r - s)**2)
implements
$$d(r,s)=\sqrt{(r+s)^2-(100-r-s)^2}.$$
Total length
length_mm = order[0] + order[-1]
adds the end clearances.
Then the loop adds every center-to-center axial separation.
Finally:
length_um = round(length_mm * 1000)
converts millimetres to micrometres.
4. Final verification
The computed length is
$$1590.933116\ldots \text{ mm}$$
which equals
$$1,590,933.116\ldots \text{ micrometres}.$$
Rounded to the nearest integer:
$$1,590,933.$$
This magnitude is reasonable:
- 21 balls each have diameters around $60$–$100$ mm,
- but heavy overlap in axial direction reduces the total to about $1.59$ m.
Answer: 1590933