{ "cells": [ { "cell_type": "markdown", "id": "01a279af-6dd7-4385-a355-a1a61d31abfa", "metadata": {}, "source": [ "
Peter Norvig
April 2026
\n", "\n", "# Project Euler Problem 3\n", "\n", "Project Euler's [Problem #3](https://projecteuler.net/problem=3) states:\n", "\n", "\n", " The prime factors of 13195 are 5, 7, 13 and 29.\n", " What is the largest prime factor of the number 600851475143?\n", "\n", "I'll describe here how I think about this problem. There are two things to get right: the general plan of how to attack this problem, and the details of how to say it in Python.\n", "\n", "## The General Plan\n", "\n", "I would like to be able to find the largest prime factor of *any* integer, not just 600851475143; I want `largest_prime_factor(n)`. Here's how I think about it:\n", "\n", "- I don't know how to immediately find the largest prime factor of *n*. (How would I know which numbers are prime?)\n", "- I do know how to find the *smallest* prime factor: just go through the integers from 2 to *n*, in order. The first integer that evenly divides *n* must be the smallest prime factor. It is a factor because it evenly divides, it is smallest because we didn't find a smaller one first, and it is prime because if it were a composite number (like 4 or 9), then we would have found one of its factors (like 2 or 3) first.\n", "- Once I have the smallest prime factor ***p*** then I know: **the largest prime factor of *n* is the maximum of *p* and the largest prime factor of *n*/*p*.**\n", "- If *n* is prime, then the largest prime factor is *n*.\n", "- If *n* is 1, then [by convention](https://oeis.org/wiki/Greatest_prime_factor_of_n) we say the greatest prime factor is 1, even though 1 is usually not considered a prime.\n" ] }, { "cell_type": "markdown", "id": "f722a30c-fbae-4c60-8327-22f188c6c1c4", "metadata": {}, "source": [ "## How to Say It in Python\n", "\n", "There are a few things to remember (or look up):\n", "- `def` is the keyword for [defining a function](https://k21academy.com/ai-ml/functions/) in Python.\n", " - `def` is followed by the function name, the function parameter(s), and the function body.\n", " - It is good practice to include a [documentation string](https://www.educative.io/answers/what-is-a-python-docstring) as the first thing in the function body, for clarification. \n", "- `range(2, n)` means all the integers starting at 2 and stopping just before *n*. E.g., `range(2, 6)` is [2, 3, 4, 5].\n", "- `n % p` means \"the remainder of *n* divided by *p*\"; e.g., `13 % 10` is `3`.\n", "- `n % p == 0` means \"is the remainder of *n* divided by *p* equal to zero?\" or \"does *p* evenly divide *n*?\" or \"is *p* a factor of *n*\"?\n", "- `n // p` means \"integer division\"; `30 // 2` is the integer 15, while `30 / 2` is the decimal number 15.0. We want integers.\n", "- `return` means to immediately return a value from a function; don't do any more statements in the function.\n", "\n", "\n", "## The Implementation and the Solution\n", "\n", "The **implementation**:" ] }, { "cell_type": "code", "execution_count": 1, "id": "249c7f13-cdf9-41a2-b0e7-8a3dec15f74b", "metadata": {}, "outputs": [], "source": [ "def largest_prime_factor(n):\n", " \"\"\"The largest prime that evenly divides n.\n", " Find the smallest prime p that evenly divides n, \n", " and return the maximum of p and the largest prime factor of n/p.\n", " For prime n and for 1, return n.\"\"\"\n", " for p in range(2, n):\n", " if n % p == 0: # n is composite\n", " return max(p, largest_prime_factor(n // p))\n", " return n # n is prime or 1" ] }, { "cell_type": "markdown", "id": "1643caa2-97d6-4ff8-8487-3285a5a42182", "metadata": {}, "source": [ "\n", "\n", "The **solution**:" ] }, { "cell_type": "code", "execution_count": 2, "id": "734de0e3-aafc-438a-8332-09741eef39aa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6857" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "largest_prime_factor(600851475143)" ] }, { "cell_type": "markdown", "id": "8bf8dd51-8e06-4572-85fa-fe42875e0305", "metadata": {}, "source": [ "## Example Factor Tree\n", "\n", "[Here](https://www.cuemath.com/numbers/factor-tree/) is the \"factor tree\" of *n* = 36:\n", "\n", "\n", "\n", "\n", "We could also represent this as a series of equations, one per line:\n", "\n", " 36 = 2 × 18\n", " 18 = 2 × 9\n", " 9 = 3 × 3\n", " 3 = 3\n", " 36 = 2 × 2 × 3 × 3\n", "\n", "\n", "Or as equations for how `largest_prime_factor(36)` is broken down, with `lpf` as an abbreviation for `largest_prime_factor`:\n", "\n", " lpf(36) = max(2, lpf(18))\n", " lpf(18) = max(2, lpf(9))\n", " lpf(9) = max(3, lpf(3))\n", " lpf(3) = 3\n", " lpf(36) = max(2, max(2, max(3, 3))) = 3" ] }, { "cell_type": "markdown", "id": "893a4079-d73d-47c9-970f-f886f4761580", "metadata": {}, "source": [ "## Some Tests\n", "\n", "Are we sure our function is correct? It is good idea to define some **tests** to gain confidence. " ] }, { "cell_type": "code", "execution_count": 3, "id": "9bedb1c9-4138-4250-90e6-77b10ba01586", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'all tests pass'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def tests():\n", " assert largest_prime_factor(1) == 1 # by convention, 1\n", " assert largest_prime_factor(2) == 2 # even prime\n", " assert largest_prime_factor(3) == 3 # odd prime\n", " assert largest_prime_factor(6) == 3 # composite\n", " assert largest_prime_factor(8) == 2 # power of 2\n", " assert largest_prime_factor(36) == 3 # example from the diagram\n", " assert largest_prime_factor(49) == 7 # square of a prime\n", " assert largest_prime_factor(97) == 97 # bigger prime\n", " assert largest_prime_factor(600851475143) == 6857 # really big number\n", " return 'all tests pass'\n", "\n", "tests()" ] }, { "cell_type": "markdown", "id": "c51ab097-5ba7-4fdf-9725-944e31aa2336", "metadata": {}, "source": [ "## Efficiency\n", "\n", "How long does it take to get our answer? We can use `%time` to see it is just a few hundred microseconds (μs):" ] }, { "cell_type": "code", "execution_count": 4, "id": "0e4a1dc6-5969-49fd-9d65-11d9801cbea2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 249 μs, sys: 20 μs, total: 269 μs\n", "Wall time: 296 μs\n" ] }, { "data": { "text/plain": [ "6857" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%time largest_prime_factor(600851475143)" ] }, { "cell_type": "markdown", "id": "c8ead8e9-199f-47c6-ad76-360b1ebdaa02", "metadata": {}, "source": [ "The algorithm should be slowest when *n* is prime, because then the `for` loop has to go all the way up to *n*. How long would it take for the largest 8-digit prime, 99,999,989?" ] }, { "cell_type": "code", "execution_count": 5, "id": "7323d528-96d7-4c05-a05d-125e99605443", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1.92 s, sys: 6.64 ms, total: 1.93 s\n", "Wall time: 1.93 s\n" ] }, { "data": { "text/plain": [ "99999989" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n8 = 99999989 # An 8-digit number that happens to be prime \n", "%time largest_prime_factor(n8)" ] }, { "cell_type": "markdown", "id": "9e894dc7-01ca-48fe-8de9-fa8791ff2af8", "metadata": {}, "source": [ "About 2 seconds. Maybe that's good enough. But could we speed things up?\n", "\n", "In trying to find a *p* that evenly divides *n*, the algorithm tests all the integers from 2 to *n*. But do we really have to test all those potential factors? No! Either *n* is prime, or it has two factors *p* and *q* such that *p* × *q* = *n*. Of those two factors, one must be less than or equal to the square root of *n*. So to determine if *n* has a prime factor other than itself, we only have to check integers up to √*n*, not all the way up to *n*. That's a big difference! for an 8-digit prime it is the difference between roughly 100 million steps versus a mere 10 thousand steps.\n", "\n", "Let's change the definition of `largest_prime_factor` to incorporate this new trick. (We will `import` the square root function, `sqrt`, from the `math` module.)" ] }, { "cell_type": "code", "execution_count": 6, "id": "b90b5407-4666-4925-99d2-6a3a6b192fac", "metadata": {}, "outputs": [], "source": [ "from math import sqrt\n", "\n", "def largest_prime_factor(n):\n", " \"\"\"The largest prime that evenly divides n.\n", " Find the smallest prime p that evenly divides n, \n", " and return the maximum of p and the largest prime factor of n/p.\n", " For prime n and for 1, return n.\"\"\"\n", " for p in range(2, int(sqrt(n) + 1)): # <<<< only need to go up to √n\n", " if n % p == 0: # n is composite\n", " return max(p, largest_prime_factor(n // p))\n", " return n # n is prime or 1" ] }, { "cell_type": "markdown", "id": "c39b08b9-06ed-49c0-a277-db248de909fd", "metadata": {}, "source": [ "Any time you change a function, you should re-run the tests to give you some confidence that you didn't introduce a bug:" ] }, { "cell_type": "code", "execution_count": 7, "id": "4026dc87-a0aa-4c75-b92a-96ec24cda1b7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'all tests pass'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tests()" ] }, { "cell_type": "markdown", "id": "6cddf393-a0fd-4cc2-9eef-3341339106bd", "metadata": {}, "source": [ "Now we can see how fast the new function is on the 8-digit prime:" ] }, { "cell_type": "code", "execution_count": 8, "id": "d3d0c13e-5c01-4112-b372-60f7eb302d25", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 238 μs, sys: 1 μs, total: 239 μs\n", "Wall time: 241 μs\n" ] }, { "data": { "text/plain": [ "99999989" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%time largest_prime_factor(n8)" ] }, { "cell_type": "markdown", "id": "82b008ea-f6b5-4bcd-8768-09c8dab089cb", "metadata": {}, "source": [ "As advertised, this is about 10,000 times faster.\n", "\n", "We should be able to handle a 16-digit prime in about 2 seconds:" ] }, { "cell_type": "code", "execution_count": 9, "id": "9b6030ef-626a-4195-aedb-ef2edac65da4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 2.05 s, sys: 7.3 ms, total: 2.05 s\n", "Wall time: 2.06 s\n" ] }, { "data": { "text/plain": [ "9927935178558959" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n16 = 9927935178558959 # Largest 16-digit prime\n", "%time largest_prime_factor(n16)" ] }, { "cell_type": "markdown", "id": "d72764be-0bde-4f2a-ad09-9d0b815a3ffe", "metadata": {}, "source": [ "## Imperative versus Declarative (or Functional) Style\n", "\n", "Our definition of largest_prime_factor mixed paradigms, using some imperative features (a for loop with a return in the middle) and some functional (an implementation of the equation `largest_prime_factor(n) = max(p, largest_prime_factor(n // p))`).\n", "\n", "Here's what it might look like if we leaned into the functional style more, making the equation more explicit:" ] }, { "cell_type": "code", "execution_count": 10, "id": "0308612c-6860-49b0-bcd6-856fa08133b1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'all tests pass'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def largest_prime_factor(n):\n", " \"\"\"The largest prime that evenly divides n.\n", " Find the smallest prime p that evenly divides n, \n", " and return the maximum of p and the largest prime factor of n/p.\n", " For prime n and for 1, return n.\"\"\"\n", " p = smallest_prime_factor(n)\n", " return 1 if n == 1 else max(p, largest_prime_factor(n // p))\n", "\n", "def smallest_prime_factor(n):\n", " \"\"\"The smallest prime that evenly divides n (or n itself if no prime divisors).\"\"\"\n", " return next((p for p in range(2, int(sqrt(n) + 1)) if n % p == 0), n)\n", "\n", "tests()" ] }, { "cell_type": "markdown", "id": "00508ec5-85db-4329-a50d-7c3176bff203", "metadata": {}, "source": [ "And here's what it looks like in an imperative style, updating `n`, `p`, and `largest` within a `while` loop that contains another `while` loop to take care of the case where *n* has *p* as a repeated factor." ] }, { "cell_type": "code", "execution_count": 13, "id": "b8907a8f-872f-4531-825c-fae9c70211c1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'all tests pass'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def largest_prime_factor(n):\n", " \"\"\"The largest prime that evenly divides n.\n", " Find the smallest prime p that evenly divides n, \n", " and return the maximum of p and the largest prime factor of n/p.\n", " For prime n and for 1, return n.\"\"\"\n", " largest = 1\n", " p = 2\n", " while p * p <= n:\n", " while n % p == 0:\n", " largest = p\n", " n = n // p\n", " p = p + 1\n", " return max(n, largest)\n", " \n", "tests()" ] }, { "cell_type": "code", "execution_count": null, "id": "5a3de62a-5e7a-4458-b754-6b2ab50e5fb9", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:base] *", "language": "python", "name": "conda-base-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.9" } }, "nbformat": 4, "nbformat_minor": 5 }