"""
Project Euler Problem 45
========================

Triangle, pentagonal, and hexagonal numbers are generated by the following
formulae:

Triangle     T[n]=n(n+1)/2   1, 3, 6, 10, 15, ...
Pentagonal   P[n]=n(3n-1)/2  1, 5, 12, 22, 35, ...
Hexagonal    H[n]=n(2n-1)    1, 6, 15, 28, 45, ...

It can be verified that T[285] = P[165] = H[143] = 40755.

Find the next triangle number that is also pentagonal and hexagonal.
"""


class GeometryHelper(object):
    """
    Helper class to calculate triangle, pentagonal or hexagonal numbers. We can
    use the brackets operator (TPH[X]) as well as the contains operator (X in
    TPH).
    """
    def __init__(self):
        # Helper[0] == 0 to make our lifes easier, as the numbers start from 1
        self.numbers = [0]

    def formulae(self, n):
        """Returns the n-th number (n>=1)"""
        return 0

    def _build(self, last):
        next_item = len(self.numbers)
        self.numbers += [self.formulae(n) for n in range(next_item, last+1)]

    def _build_until_number(self, last_p):
        next_item = len(self.numbers)
        while self.numbers[-1] < last_p:
            self.numbers.append(self.formulae(next_item))
            next_item += 1

    def __contains__(self, p):
        self._build_until_number(p)
        return p in self.numbers

    def __getitem__(self, n):
        if n >= len(self.numbers):
            self._build(n)
        return self.numbers[n]


class TriangleHelper(GeometryHelper):
    def formulae(self, n):
        """Returns the n-th triangle number (n>1)"""
        return (n*(n+1))//2


class PentagonalHelper(GeometryHelper):
    def formulae(self, n):
        """Returns the n-th triangle number (n>1)"""
        return (n*(3*n-1))//2


class HexagonalHelper(GeometryHelper):
    def formulae(self, n):
        """Returns the n-th triangle number (n>1)"""
        return n*(2*n-1)

# Test
T = TriangleHelper()
P = PentagonalHelper()
H = HexagonalHelper()
assert [T[i] for i in range(1, 6)] == [1, 3, 6, 10, 15]
assert [P[i] for i in range(1, 6)] == [1, 5, 12, 22, 35]
assert [H[i] for i in range(1, 6)] == [1, 6, 15, 28, 45]

assert 15 in T
assert 16 not in T

assert 35 in P
assert 36 not in P

assert 45 in H
assert 46 not in H


def main():
    # Simply iterate over the triangle numbers and check if the result number is
    # also a pentagonal and hexagonal number

    # We already know that T[285] = P[165] = H[143] = 40755:
    assert T[285] == P[165] == H[143] == 40755
    assert 40755 in T
    assert 40755 in P
    assert 40755 in H

    # So we can start from 286
    n = 286
    while True:
        t = T[n]
        if t in P and t in H:
            return t
        n += 1

if __name__ == '__main__':
    print(main())
