diff --git a/README.rst b/README.rst index c240fa5d..3645b287 100644 --- a/README.rst +++ b/README.rst @@ -107,7 +107,7 @@ Olivia's Project Euler Solutions | | | | |Lu-Cov| |br| | | | | | |LuaCheck| | +------------+----------------------------+--------+-------------------+ - | Python | CPython 3.6+ |br| | 85 | |Python| |br| | + | Python | CPython 3.6+ |br| | 86 | |Python| |br| | | | Pypy 3.6+ |br| | | |Py-Cov| |br| | | | GraalPy 23.1+ |br| | | |CodeQL| |br| | | | Browser [#]_ | | |PythonLint| | diff --git a/_data/answers.tsv b/_data/answers.tsv index 6dcb7fe2..49e3af79 100644 --- a/_data/answers.tsv +++ b/_data/answers.tsv @@ -71,6 +71,7 @@ ID type size answer 77 int 8 71 79 int 32 73162890 81 int 32 427337 +85 int 16 2772 87 int 32 1097343 89 int 16 743 92 int 32 8581146 diff --git a/docs/index.rst b/docs/index.rst index fcec2f99..1040346c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -222,6 +222,8 @@ Problems Solved +-----------+-----------+------------+------------+------------+------------+------------+------------+------------+------------+ |:prob:`081`| | | | | | | |:py-d:`0081`| | +-----------+-----------+------------+------------+------------+------------+------------+------------+------------+------------+ + |:prob:`085`| | | | | | | |:py-d:`0085`| | + +-----------+-----------+------------+------------+------------+------------+------------+------------+------------+------------+ |:prob:`087`| | | | | | | |:py-d:`0087`| | +-----------+-----------+------------+------------+------------+------------+------------+------------+------------+------------+ |:prob:`089`| | | | | | | |:py-d:`0089`| | diff --git a/docs/src/python/p0085.rst b/docs/src/python/p0085.rst new file mode 100644 index 00000000..7be046ca --- /dev/null +++ b/docs/src/python/p0085.rst @@ -0,0 +1,23 @@ +Python Implementation of Problem 85 +=================================== + +View source code :source:`python/src/p0085.py` + +Includes +-------- + +- :external:py:func:`math.ceil` +- :external:py:func:`math.sqrt` + +Problem Solution +---------------- + +.. automodule:: python.src.p0085 + :members: + :undoc-members: + +.. literalinclude:: ../../../python/src/p0085.py + :language: python + :linenos: + +.. tags:: geometry, packing, combinatorics diff --git a/python/README.rst b/python/README.rst index dc9757a9..2e641923 100644 --- a/python/README.rst +++ b/python/README.rst @@ -198,6 +198,7 @@ Problems Solved - ☒ `77 <./src/p0077.py>`__ - ☒ `79 <./src/p0079.py>`__ - ☒ `81 <./src/p0081.py>`__ +- ☒ `85 <./src/p0085.py>`__ - ☒ `87 <./src/p0087.py>`__ - ☒ `89 <./src/p0089.py>`__ - ☒ `92 <./src/p0092.py>`__ diff --git a/python/src/lib/primes.py b/python/src/lib/primes.py index 7e5c7d7a..8f4e131a 100644 --- a/python/src/lib/primes.py +++ b/python/src/lib/primes.py @@ -77,6 +77,11 @@ def modified_eratosthenes() -> Iterator[int]: def prime_factors(num: int) -> Iterator[int]: """Iterates over the prime factors of a number. + .. note:: + + For the purposes of this function, :math:`-1` is included as a factor for negative numbers, and :math:`0` is + yielded as a special case. In general, prime factors are strictly positive integers greater than 1. + It takes :math:`O(m \\cdot \\log(m)^{1.585} \\cdot \\log(\\log(m)))` time to generate the needed primes, where :math:`m = \\sqrt{n}`, so :math:`O(\\sqrt{n} \\cdot \\log(\\sqrt{n})^{1.585} \\cdot \\log(\\log(\\sqrt{n})))`, which simplifies to :math:`O(\\sqrt{n} \\cdot \\log(n)^{1.585} \\cdot \\log(\\log(n)))` operations.""" @@ -115,7 +120,7 @@ def is_prime( :math:`O(\\log(n)^{1.585} \\cdot \\log(\\log(n)))`. If count is :math:`c` and ``distinct=False``, it runs in :math:`O(c \\cdot \\log(n)^{1.585} \\cdot \\log(\\log(n)))`. Otherwise, it runs in :math:`O(\\sqrt{n} \\cdot \\log(n)^{1.585} \\cdot \\log(\\log(n)))` time.""" - if num in (0, 1): + if num < 2: return False factors = iter(prime_factors(num)) if count == 1: @@ -145,7 +150,7 @@ def primes_and_negatives(*args: int) -> Iterator[int]: def fast_totient(n: int, factors: Optional[Iterable[int]] = None) -> int: - """A shortcut method to calculate Euler's totient function which assumes n has *distinct* prime factors. + """A faster method to calculate Euler's totient function when n has *distinct* prime factors. It runs exactly 1 multiplication and 1 subtraction per prime factor, giving a worst case of :math:`O(\\sqrt{n} \\cdot \\log(n)^{3.17} \\cdot \\log(\\log(n)))`.""" @@ -159,12 +164,15 @@ def _reduce_factors(x: Fraction, y: int) -> Fraction: def totient(n: int) -> int: """Calculates Euler's totient function in the general case. + This function computes the number of integers less than n that are coprime to n. It handles the general case, + including repeated prime factors. + Takes :math:`O(\\log(n))` fraction multiplications, each of which is dominated by two GCDs, which run at :math:`O(\\log(\\min(a, b))^2)`. Given that the max factor is :math:`\\sqrt{n}`, we can simplify this to a worst case of :math:`O(\\log(n) \\cdot \\log(\\sqrt{n}))^2 = O(\\log(n) \\cdot \\tfrac{1}{2} \\log(n)^2) = O(\\log(n)^3)`. Computing the prime factors themselves takes :math:`O(\\sqrt{n} \\cdot \\log(n)^{1.585} \\cdot \\log(\\log(n)))` - when the cache is not initialized, but on all future runs will take :math:`O(\\log(n))` time. + when the global prime cache is not initialized, but on all future runs will take :math:`O(\\log(n))` time. This combines to give us :math:`O(\\sqrt{n} \\cdot \\log(n)^{4.585} \\cdot \\log(\\log(n)))` time when the cache is stale, and :math:`O(\\log(n)^4)` time on future runs.""" diff --git a/python/src/p0081.py b/python/src/p0081.py index a0e2f9c9..6fed0c05 100644 --- a/python/src/p0081.py +++ b/python/src/p0081.py @@ -1,10 +1,18 @@ """ -Project Euler Template +Project Euler Problem 81 In the 5 by 5 matrix below, the minimal path sum from the top left to the bottom right, by only moving to the right and down, is indicated in bold red and is equal to 2427. - +.. math:: + + \\begin{pmatrix} + \\color{red}{131} & 673 & 234 & 103 & 18\\\\ + \\color{red}{201} & \\color{red}{96} & \\color{red}{342} & 965 & 150\\\\ + 630 & 803 & \\color{red}{746} & \\color{red}{422} & 111\\\\ + 537 & 699 & 497 & \\color{red}{121} & 956\\\\ + 805 & 732 & 524 & \\color{red}{37} & \\color{red}{331} + \\end{pmatrix} Find the minimal path sum from the top left to the bottom right by only moving right and down in matrix.txt (right click and "Save Link/Target As..."), a 31K text file containing an 80 by 80 matrix. @@ -36,6 +44,6 @@ def min_path_sum( def main() -> int: setup: List[Sequence[int]] = [] for raw_line in get_data_file('p0081_matrix.txt').splitlines(): - line = raw_line.rstrip('\n') + line = raw_line.rstrip('\\n') setup.append(tuple(int(x) for x in line.split(','))) return min_path_sum(tuple(setup), {}) diff --git a/python/src/p0085.py b/python/src/p0085.py new file mode 100644 index 00000000..967d82f6 --- /dev/null +++ b/python/src/p0085.py @@ -0,0 +1,28 @@ +""" +Project Euler Problem 85 + +Problem: + +By counting carefully it can be seen that a rectangular grid measuring 3 by 2 contains eighteen rectangles: + +.. image:: https://projecteuler.net/resources/images/0085.png + +Although there exists no rectangular grid that contains exactly two million rectangles, find the area of the grid with the nearest solution. +""" +from math import ceil, sqrt + + +def num_rectangles(x: int, y: int) -> int: + answer = 0 + for a in range(1, x + 1): + for b in range(1, y + 1): + answer += (x - a + 1) * (y - b + 1) + return answer + + +def main(tolerance: int = 2) -> int: + for x in range(1, ceil(sqrt(2000000))): + for y in range(1, x): + if abs(num_rectangles(x, y) - 2000000) <= tolerance: + return x * y + return -1 diff --git a/python/test_euler.py b/python/test_euler.py index e5b07f3a..ae860761 100644 --- a/python/test_euler.py +++ b/python/test_euler.py @@ -27,6 +27,7 @@ *range(76, 78), 79, 81, + 85, 87, 89, 92,