From d682ac4aeb097aee00ce1679dfb8e4e10620b80f Mon Sep 17 00:00:00 2001 From: filifa Date: Thu, 26 Jun 2025 20:18:42 -0400 Subject: [PATCH] sieve instead --- notebooks/problem0070.ipynb | 78 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/notebooks/problem0070.ipynb b/notebooks/problem0070.ipynb index 0cd34ed..5441447 100644 --- a/notebooks/problem0070.ipynb +++ b/notebooks/problem0070.ipynb @@ -32,39 +32,57 @@ "As in [problem 69](https://projecteuler.net/problem=69),\n", "$$\\phi(n) = n\\prod_{p | n} \\left(1 - \\frac{1}{p}\\right)$$\n", "\n", - "Rather than calculate the totients of every single number up to $10^7$, we'll start with just the primes - for a prime $p$, $\\phi(p) = p-1$. We'll store these primes in a [min-heap](https://en.wikipedia.org/wiki/Heap_(data_structure)) ordered by $\\frac{p}{\\phi(p)}$." + "We can calculate the totients of the numbers up to $10^7$ using a very similar approach to the [sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) for generating prime numbers (see [problem 10](https://projecteuler.net/problem=10)).\n", + "\n", + "We'll initialize a list of numbers from 0 to $10^7$." ] }, { "cell_type": "code", "execution_count": 2, - "id": "244e05bd", + "id": "524fca40", "metadata": {}, "outputs": [], "source": [ - "import heapq\n", - "\n", "limit = 10^7\n", - "\n", - "primes = prime_range(limit)\n", - "queue = [(p / (p - 1), (p, p - 1)) for p in primes]\n", - "heapq.heapify(queue)" + "totients = list(range(0, limit))" ] }, { "cell_type": "markdown", - "id": "9a057da0", + "id": "88ef58dd", "metadata": {}, "source": [ - "Then we'll search to find the value $n$ with the smallest ratio that is also a digit permutation of its totient. We'll check composite values by pushing $np$ to the queue for each prime $p$.\n", - "\n", - "When we find a value that is a digit permutation of its totient, we'll know that it also has the smallest ratio and can stop." + "Iterating $n$ from 2 to $10^7$, if `totients[n] == n`, then $n$ is prime, and we'll update its totient and all its multiples using the above formula. If `totients[n] != n`, then we'll check if $n/\\phi(n)$ is small and if $\\phi(n)$ is a permutation of $n$, keeping track of the best answer so far." ] }, { "cell_type": "code", "execution_count": 3, - "id": "99c4267a", + "id": "aa071cc4", + "metadata": {}, + "outputs": [], + "source": [ + "answer = None\n", + "ratio = float('inf')\n", + "\n", + "for n in range(2, limit):\n", + " if totients[n] != n:\n", + " r = n / totients[n]\n", + " if r < ratio and is_permutation_pair(n, totients[n]):\n", + " ratio = r\n", + " answer = n\n", + "\n", + " continue\n", + "\n", + " for p in range(n, limit, n):\n", + " totients[p] -= totients[p] // n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d4819aa5", "metadata": {}, "outputs": [ { @@ -73,38 +91,12 @@ "8319823" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "answer = None\n", - "visited = set()\n", - "while queue != []:\n", - " _, (n, totient) = heapq.heappop(queue)\n", - " \n", - " if n in visited:\n", - " continue\n", - " visited.add(n)\n", - " \n", - " if is_permutation_pair(n, totient):\n", - " answer = n\n", - " break\n", - " \n", - " for p in primes:\n", - " q = n * p\n", - " if q >= limit:\n", - " break\n", - " \n", - " if n % p != 0:\n", - " new_totient = totient * (p - 1)\n", - " else:\n", - " new_totient = totient * p\n", - " \n", - " ratio = q / new_totient\n", - " heapq.heappush(queue, (ratio, (q, new_totient)))\n", - " \n", "answer" ] }, @@ -113,7 +105,11 @@ "id": "40cf1e01", "metadata": {}, "source": [ - "Note: lots of people in the problem thread make the assumption that the answer must be a [semiprime](https://en.wikipedia.org/wiki/Semiprime). However, Steendor points out that for certain upper bounds, this assumption does not hold. This solution avoids making that assumption." + "Note: lots of people in the problem thread make the assumption that the answer must be a [semiprime](https://en.wikipedia.org/wiki/Semiprime). However, Steendor points out that for certain upper bounds, this assumption does not hold. For instance, $2817 = 3^2 \\times 313$ and $\\phi(2817) = 1872$, and 2817/1872 is the lowest ratio until 2991. 2817 may be an exception rather than the norm (all the other numbers in A102018 up to $10^8$ are semiprimes); nevertheless, this solution avoids making the assumption.\n", + "\n", + "## Relevant sequences\n", + "* All numbers $n$ such that $\\phi(n)$ is a digit permutation: [A115921](https://oeis.org/A115921)\n", + "* Subsequence of A115921 such that $n/\\phi(n)$ is a record low: [A102018](https://oeis.org/A102018)" ] } ],