\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, at alevel suitable for a novice programmer. 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*. \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 largest prime factor is 1, even though 1 is usually not considered a prime."
]
},
{
"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",
" If n is prime or if n = 1, this will 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": [
"We can represent the execution of the program as a series of equations, one per line. Using `F` as an abbreviation for `largest_prime_factor`, here is hjow we find the largest prime factor of 360:\n",
"\n",
" F(360) = max(2, F(180))\n",
" F(180) = max(2, F(90))\n",
" F(90) = max(2, F(45))\n",
" F(45) = max(3, F(15))\n",
" F(15) = max(3, F(5))\n",
" F(5) = 5\n",
" F(36) = max(2, max(2, max(2, 3, 3, 5))) = 5"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a4aaf952-bf6a-4dec-a950-5c573e1fbef0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"360"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2 * 2 * 2 * 3 * 3 * 5"
]
},
{
"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": 4,
"id": "9bedb1c9-4138-4250-90e6-77b10ba01586",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[11, 'out of', 11, 'tests pass']"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def tests():\n",
" \"\"\"Test largest_prime_factor.\"\"\"\n",
" cases = {1: 1, # by convention, 1\n",
" 2: 2, # even prime\n",
" 3: 3, # odd prime\n",
" 6: 3, # composite\n",
" 32: 2, # power of 2\n",
" 49: 7, # square of a prime\n",
" 97: 97, # bigger prime\n",
" 99991: 99991, # even bigger prime\n",
" 97**9: 97, # even bigger power of a prime\n",
" 360: 5, # test case for equations above\n",
" 600851475143: 6857 # Project Euler #3\n",
" }\n",
" correct = sum(largest_prime_factor(n) == cases[n] for n in cases)\n",
" return [correct, 'out of', len(cases), '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": 5,
"id": "0e4a1dc6-5969-49fd-9d65-11d9801cbea2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 181 μs, sys: 0 ns, total: 181 μs\n",
"Wall time: 183 μs\n"
]
},
{
"data": {
"text/plain": [
"6857"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time largest_prime_factor(600851475143)"
]
},
{
"cell_type": "markdown",
"id": "c8ead8e9-199f-47c6-ad76-360b1ebdaa02",
"metadata": {},
"source": [
"The algorithm is slowest when *n* is prime, because 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": 6,
"id": "7323d528-96d7-4c05-a05d-125e99605443",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.9 s, sys: 18.4 ms, total: 1.92 s\n",
"Wall time: 1.92 s\n"
]
},
{
"data": {
"text/plain": [
"99999989"
]
},
"execution_count": 6,
"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": 7,
"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",
" If n is prime or if n = 1, this will 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": 8,
"id": "4026dc87-a0aa-4c75-b92a-96ec24cda1b7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[11, 'out of', 11, 'tests pass']"
]
},
"execution_count": 8,
"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": 9,
"id": "d3d0c13e-5c01-4112-b372-60f7eb302d25",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 196 μs, sys: 0 ns, total: 196 μs\n",
"Wall time: 196 μs\n"
]
},
{
"data": {
"text/plain": [
"99999989"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time largest_prime_factor(n8)"
]
},
{
"cell_type": "markdown",
"id": "82b008ea-f6b5-4bcd-8768-09c8dab089cb",
"metadata": {},
"source": [
"As predicted, 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": 10,
"id": "9b6030ef-626a-4195-aedb-ef2edac65da4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.96 s, sys: 19.3 ms, total: 1.97 s\n",
"Wall time: 1.97 s\n"
]
},
{
"data": {
"text/plain": [
"9927935178558959"
]
},
"execution_count": 10,
"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](https://en.wikipedia.org/wiki/Imperative_programming) features (a for loop with a return in the middle) and some [functional](https://en.wikipedia.org/wiki/Functional_programming) features (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": 11,
"id": "0308612c-6860-49b0-bcd6-856fa08133b1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[11, 'out of', 11, 'tests pass']"
]
},
"execution_count": 11,
"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",
" If n is prime or if n = 1, this will 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": 12,
"id": "b8907a8f-872f-4531-825c-fae9c70211c1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[11, 'out of', 11, 'tests pass']"
]
},
"execution_count": 12,
"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",
" If n is prime or if n = 1, this will 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()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"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
}