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

Project Euler Problem 222

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