Triangle Fraction

A puzzle addressed during a MathsJam session (January 2016).

Triangle Fraction Problem

Three equilateral triangles. Which fraction is shaded?

Found on Twitter, posted by @math8_teacher

Well, time for some geometry then. Hmm what about being lazy instead and let Python do the calculations? :)

In [1]:
from sympy.geometry import Point, Segment, Triangle, intersection
from sympy import sqrt

import matplotlib.pyplot as plt
%matplotlib inline 
import seaborn as sns
In [2]:
def ratio(side):

    # Height of an equilateral triangle
    height = side * sqrt(3) / 2

    # Points of the triangles on the base line
    p0 = Point(0, 0)
    p1 = Point(side, 0)
    p2 = Point(2 * side, 0)
    p3 = Point(3 * side, 0)

    # Points of the triangles on the "top" line
    p4 = Point(0.5 * (p1.x + p0.x), height)
    p5 = Point(0.5 * (p2.x + p1.x), height)
    p6 = Point(0.5 * (p3.x + p2.x), height)
    
    # The 3 segments we are only interested in
    # (that will intersect the last one)
    s1 = Segment(p1, p4)
    s2 = Segment(p1, p5)
    s3 = Segment(p2, p5)

    # The "big" segment that intersects everything
    s4 = Segment(p0, p6)

    # Intersections of the "big" segment with the others
    i1 = intersection(s1, s4)[0]
    i2 = intersection(s2, s4)[0]
    i3 = intersection(s3, s4)[0]

    # The normal triangles
    tri1 = Triangle(p0, p1, p4)
    tri2 = Triangle(p1, p2, p5)
    tri3 = Triangle(p2, p3, p6)

    # The triangles constructed with the intersection points
    tri4 = Triangle(p0, i1, p4)
    tri5 = Triangle(i2, i3, p5)

    res = (tri4.area + tri5.area) / (tri1.area + tri2.area + tri3.area)
    return res
In [9]:
# Calculate for 3 triangles of side 1
print('Ratio:', ratio(1))
Ratio: 5/18
In [4]:
# The ratio should not depend on the side of the triangles
for i in range(2, 10):
    assert ratio(i) == ratio(1)
    
print('All good!')
All good!

Extension for 4, 5, and more triangles

The figure only shows 3 triangles. And for now the code does the calculation in a very manual way, defining the points, segments, triangles one by one.

Is it possible to rewrite the ratio function to be generic and handle the case for more triangles?

In [5]:
def ratio_n(n, side):

    # Height of an equilateral triangle
    height = side * sqrt(3) / 2

    ps = []
    # Points of the triangles on the base line
    for k in range(n + 1):
        ps.append(Point(side * k, 0))
    
    # Points of the triangles on the "top" line
    for k in range(n):
        ps.append(Point(0.5 * (ps[k+1].x + ps[k].x), height))
        
    # The segments we are only interested in
    segs = []
    for k in range(1, n):
        segs.append(Segment(ps[k], ps[k+n]))
        segs.append(Segment(ps[k], ps[k+n+1]))
        
    # The "big" segment that intersects everything
    big = Segment(ps[0], ps[-1])
    
    # The normal triangles
    tris = []
    for k in range(n):
        tris.append(Triangle(ps[k], ps[k+1], ps[k+1+n]))

    # The triangles constructed with the intersection points
    shaded = []
    for k in range(n-1):
        if k == 0:
            it = intersection(segs[0], big)[0]
            shaded.append(Triangle(ps[k], it, ps[k+1+n]))
        else:
            kk = 2 * k
            it1 = intersection(segs[kk], big)[0]
            it2 = intersection(segs[kk-1], big)[0]
            shaded.append(Triangle(it1, it2, ps[k+1+n]))
            
    area_shaded = sum([abs(tri.area) for tri in shaded])
    area_total = sum([abs(tri.area) for tri in tris])
    res =  area_shaded / area_total 
    return res

Let's first verify the function calcultes the same result found above for 3 triangles.

In [6]:
print('Ratio for 3 triangles with the generic function:', ratio_n(3, 1))
Ratio for 3 triangles with the generic function: 5/18

Good. Now what happens when there are more triangles?

In [7]:
SIDE = 10
LIMIT = 31  # Up to 30 triangles
ratios = []
xs = list(range(3, LIMIT))
for i in xs:
    ratio_i = ratio_n(i, SIDE)
    ratios.append(ratio_i)
    print('Ratio for', i, 'triangles:', ratio_i, '~=', ratio_i.evalf())
Ratio for 3 triangles: 5/18 ~= 0.277777777777778
Ratio for 4 triangles: 7/24 ~= 0.291666666666667
Ratio for 5 triangles: 3/10 ~= 0.300000000000000
Ratio for 6 triangles: 11/36 ~= 0.305555555555556
Ratio for 7 triangles: 13/42 ~= 0.309523809523810
Ratio for 8 triangles: 5/16 ~= 0.312500000000000
Ratio for 9 triangles: 17/54 ~= 0.314814814814815
Ratio for 10 triangles: 19/60 ~= 0.316666666666667
Ratio for 11 triangles: 7/22 ~= 0.318181818181818
Ratio for 12 triangles: 23/72 ~= 0.319444444444444
Ratio for 13 triangles: 25/78 ~= 0.320512820512820
Ratio for 14 triangles: 9/28 ~= 0.321428571428571
Ratio for 15 triangles: 29/90 ~= 0.322222222222222
Ratio for 16 triangles: 31/96 ~= 0.322916666666667
Ratio for 17 triangles: 11/34 ~= 0.323529411764706
Ratio for 18 triangles: 35/108 ~= 0.324074074074074
Ratio for 19 triangles: 37/114 ~= 0.324561403508772
Ratio for 20 triangles: 13/40 ~= 0.325000000000000
Ratio for 21 triangles: 41/126 ~= 0.325396825396825
Ratio for 22 triangles: 43/132 ~= 0.325757575757576
Ratio for 23 triangles: 15/46 ~= 0.326086956521739
Ratio for 24 triangles: 47/144 ~= 0.326388888888889
Ratio for 25 triangles: 49/150 ~= 0.326666666666667
Ratio for 26 triangles: 17/52 ~= 0.326923076923077
Ratio for 27 triangles: 53/162 ~= 0.327160493827161
Ratio for 28 triangles: 55/168 ~= 0.327380952380952
Ratio for 29 triangles: 19/58 ~= 0.327586206896552
Ratio for 30 triangles: 59/180 ~= 0.327777777777778
In [8]:
plt.figure(figsize=(20,20), dpi=80)
plt.plot(xs, ratios, 'o-')
plt.xlabel('Number of equilateral triangles')
plt.ylabel('Percentage of the shaded area')
plt.title('Percentage of shaded area per number of equilateral triangles')
Out[8]:
<matplotlib.text.Text at 0x7f577bb172e8>

Observations

That's quite interesting! With some optimizations in the ratio_n function, we could calculate the ratio for more triangles.

But looking at the graph, we can ask at least two questions:

  • Does the ratio converge to 1/3? (0.333333...)
  • If yes, how to prove it?

I will let these two questions as an open challenge :)