diff --git a/ipynb/Euler.ipynb b/ipynb/Euler.ipynb
new file mode 100644
index 0000000..1ebf399
--- /dev/null
+++ b/ipynb/Euler.ipynb
@@ -0,0 +1,5106 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "2f9c68a7-b4d5-42cd-8d83-c3efec4b2498",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "
Peter Norvig
2000โ2026
\n",
+ "\n",
+ "# Project Euler\n",
+ "\n",
+ "[Project Euler](https://projecteuler.net) is a collection of math/programming [problems](https://projecteuler.net/archives). Here are my solutions for most of the first 100 problems. (I've done some of the higher-numbereed problems, but the project requests that solutions for them remain private). I think my solutions are reasonably clear, concise, and efficient. \n",
+ "\n",
+ "There are 5 problems I did not complete. I need to study some math before getting to them.\n",
+ "- 64, 65: continued fractions\n",
+ "- 66, 94, 100: Pell's equation\n",
+ "\n",
+ "Project Euler suggests that solutions should run in a minute or less, but that recommendation was written when computers were much slower than today, so I'm aiming for one second per problem. I achieved that for every problem except 78, which takes about 3 seconds.\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9d0d32cd-ab1a-4846-8b6b-3b5bc97fe19b",
+ "metadata": {},
+ "source": [
+ "## Imports\n",
+ "\n",
+ "I use the following modules:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3e3208be-1e3b-46ff-9e03-6894ea10e38c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ast import literal_eval\n",
+ "from bisect import bisect_right\n",
+ "from collections import Counter, defaultdict, deque\n",
+ "from collections.abc import Iterable, Iterator\n",
+ "from fractions import Fraction\n",
+ "from functools import cache\n",
+ "from itertools import permutations, combinations, combinations_with_replacement, cycle\n",
+ "from itertools import islice, repeat, takewhile, product as crossproduct, count as integers\n",
+ "from math import gcd, lcm, factorial, comb, sqrt, isqrt, prod, log, inf\n",
+ "from pathlib import Path\n",
+ "from sympy import factorint, sieve, divisors, primerange, isprime, prime\n",
+ "from statistics import mean, median\n",
+ "\n",
+ "import decimal\n",
+ "import heapq\n",
+ "import numpy as np\n",
+ "import random\n",
+ "import re\n",
+ "import time"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e448c3d9-9f78-414c-883c-1eb7aba5fb37",
+ "metadata": {},
+ "source": [
+ "## Utilities\n",
+ "\n",
+ "The following utilities are used in multiple problems. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "e7a05459-c235-44cb-8997-a85be0074339",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "#### Constants \n",
+ "\n",
+ "million = 1000000\n",
+ "primes = sieve # from sympy module\n",
+ "one_nine = '123456789'\n",
+ "digits = '0123456789'\n",
+ "\n",
+ "#### Functions on iterables\n",
+ "\n",
+ "def quantify(iterable, predicate=bool) -> int:\n",
+ " \"\"\"Count how many items in the iterable are true-ish, by themselves, or according to predicate..\"\"\"\n",
+ " return sum(map(predicate, iterable))\n",
+ "\n",
+ "def first(iterable, default=False) -> object:\n",
+ " \"\"\"Return the first element of the iterable, or the default if it is empty.\"\"\"\n",
+ " return next(iter(iterable), default)\n",
+ "\n",
+ "def first_true(iterable, default=False, predicate=None):\n",
+ " \"\"\"Returns the first true value (according to predicate) or the default if there is no true value.\"\"\"\n",
+ " return next(filter(predicate, iterable), default)\n",
+ "\n",
+ "def ints(start, end) -> range:\n",
+ " \"\"\"The integers from start to end, inclusive. Equal to range(start, end+1)\"\"\"\n",
+ " return range(start, end+1)\n",
+ "\n",
+ "def powerset(iterable) -> Iterable[tuple]:\n",
+ " \"\"\"Yield all subsets of the iterable.\"\"\"\n",
+ " items = list(iterable)\n",
+ " for r in range(len(items)+1):\n",
+ " for c in combinations(items, r):\n",
+ " yield c\n",
+ "\n",
+ "def groupby(iterable, key=lambda x: x) -> dict[object, list]:\n",
+ " \"\"\"Return a dict of {key(item): [items...]} grouping all the items in the iterable.\"\"\"\n",
+ " groups = defaultdict(list)\n",
+ " for item in iterable:\n",
+ " groups[key(item)].append(item)\n",
+ " return groups\n",
+ "\n",
+ "def shuffled(iterable) -> list:\n",
+ " \"\"\"Randomly shuffle the iterable.\"\"\"\n",
+ " items = list(iterable)\n",
+ " random.shuffle(items)\n",
+ " return items\n",
+ "\n",
+ "def sliding_window(iterable, n) -> Iterable[tuple]:\n",
+ " \"\"\"Collect data into overlapping fixed-length chunks or blocks.\"\"\"\n",
+ " # sliding_window('ABCDEFG', 3) โ ABC BCD CDE DEF EFG\n",
+ " iterator = iter(iterable)\n",
+ " window = deque(islice(iterator, n - 1), maxlen=n)\n",
+ " for x in iterator:\n",
+ " window.append(x)\n",
+ " yield tuple(window)\n",
+ "\n",
+ "def nth(iterable, n, default=None) -> object:\n",
+ " \"\"\"Returns the nth item of an iterable or a default value (0-indexed).\"\"\"\n",
+ " return next(islice(iterable, n, None), default)\n",
+ " \n",
+ "#### Functions on strings\n",
+ "\n",
+ "def palindromic(x: object) -> bool:\n",
+ " \"\"\"Is x (converted to a string) a palindrome (same forwards and backwards)?\"\"\"\n",
+ " s = str(x)\n",
+ " return s == s[::-1]\n",
+ "\n",
+ "def is_permutation(a, b) -> bool: \n",
+ " \"\"\"Are a and b (as printed strings) permutations of each other?\"\"\"\n",
+ " return sorted(str(a)) == sorted(str(b))\n",
+ "\n",
+ " \n",
+ "def concat(iterable) -> str:\n",
+ " \"\"\"Concatenate the items, converting each to a string first.\"\"\"\n",
+ " return ''.join(map(str, iterable))\n",
+ "\n",
+ "def read(source: Path|str) -> str:\n",
+ " \"\"\"Read text from a path, if given, or just return the str if given that.\n",
+ " (The option of a str is so you can pass in some simple text for debugging.)\"\"\"\n",
+ " match source:\n",
+ " case Path():\n",
+ " return (\"DATA\" / source).read_text()\n",
+ " case str():\n",
+ " return source # Don't read from a file; use this text as is. \n",
+ "\n",
+ "Matrix = list[list[int]]\n",
+ "\n",
+ "def parse_matrix(text: str) -> Matrix:\n",
+ " \"\"\"Read from a string that represents an array of ints, with each line being a row of items, \n",
+ " space and/or comma delimited.\"\"\"\n",
+ " text = text.replace(',', ' ') # change commas to space\n",
+ " return [[int(x) for x in line.split()]\n",
+ " for line in text.strip().splitlines()]\n",
+ "\n",
+ "### Functions on digit strings\n",
+ "\n",
+ "def digitlist(n: int) -> list[int]:\n",
+ " \"\"\"Return a list of the digits in the decimal representation of n.\"\"\"\n",
+ " return [int(x) for x in str(n)]\n",
+ "\n",
+ "def digitsum(n: int) -> int: \n",
+ " \"\"\"Sum of the digits in n.\"\"\"\n",
+ " # I originally had just `sum(digitlist(n))`, but I use this often, and this is 50% faster.\n",
+ " total = 0\n",
+ " while n:\n",
+ " n, ones = divmod(n, 10)\n",
+ " total += ones\n",
+ " return total\n",
+ "\n",
+ "def sorted_characters(x) -> str:\n",
+ " \"\"\"Convert x to a string and put the characters in canonical sorted order.\"\"\"\n",
+ " # This is useful e.g. in Problem 92: both 12345 and 54321 have the same sum of squares of digits.\n",
+ " # sorted_characters will map both to '12345', increasing cache hits.\n",
+ " return concat(sorted(str(x))) \n",
+ "\n",
+ "#### Squares and other polygonal numbers\n",
+ "\n",
+ "def triangle(n): return n * (n + 1) // 2 # triangular number\n",
+ "def square(n): return n * n # square number\n",
+ "def pentagonal(n): return n * (3 * n - 1) // 2 # pentagonal number\n",
+ "def hexagonal(n): return n * (2 * n - 1) # hexagonal number\n",
+ "def heptagonal(n): return n * (5 * n - 3) // 2 # heptagonal number\n",
+ "def octagonal(n): return n * (3 * n - 2) # octagonal number"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f80983a4-f7c9-4132-9295-8aeb4d667e96",
+ "metadata": {},
+ "source": [
+ "## Answer Reporting\n",
+ "\n",
+ "Each of my problem solutions Iends with a line of code like `answer(euler_1, 233168)`, which means to call `euler_1()` and check that the result is the expected value, 233168. The results (including the run time) are packaged up into an instance of the `answer` class and stored in `ANSWERS[1]`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "b1af2e45-5f72-495e-9b49-5837576234dd",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "ANSWERS = {} # dict of {problem number: answer_object}\n",
+ "\n",
+ "class answer:\n",
+ " \"\"\"Verify that calling `euler_n()` computes the `expected` result. Store results in `ANSWERS`.\"\"\"\n",
+ " def __init__(self, euler_n, expected=None):\n",
+ " n = int(euler_n.__name__.split('_')[1])\n",
+ " self.euler_n, self.expected, self.n = euler_n, expected, n\n",
+ " ANSWERS[n] = self\n",
+ " self.run()\n",
+ " \n",
+ " def run(self) -> bool:\n",
+ " \"\"\"Check if euler_n() gets the expected result; record run time.\"\"\"\n",
+ " start = time.time()\n",
+ " self.got = self.euler_n()\n",
+ " self.msecs = round((time.time() - start) * 1000)\n",
+ " \n",
+ " def __repr__(self) -> str:\n",
+ " check = ('โ
' if (self.got == self.expected) else f'โ (expected {self.expected})')\n",
+ " speed = ('๐' if self.msecs > 1000 else '') # Snail icon for all times over a second\n",
+ " title = (self.euler_n.__doc__ or '?:').split(':')[0]\n",
+ " return f'{self.n:3}: {title:40} {self.msecs:6,d} msec โ {self.got:<16} {check} {speed}'\n",
+ "\n",
+ "def summary(problems=range(1, 101)) -> None:\n",
+ " \"\"\"Summary report on the answers.\"\"\"\n",
+ " T = [answer.msecs / 1000 for answer in ANSWERS.values()]\n",
+ " print(f'Missing problems: {sorted(set(problems) - set(ANSWERS)) or None}\\n'\n",
+ " f'Run time in seconds: '\n",
+ " f'total: {sum(T):.1f}, max: {max(T):.1f}, mean: {mean(T):.3f}, median: {median(T):.3f}\\n')\n",
+ "\n",
+ " for i in sorted(ANSWERS):\n",
+ " print(ANSWERS[i])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b0328f8e-1d6e-4abb-8ebd-470ccfc5023f",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 1โ10"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0000",
+ "metadata": {},
+ "source": [
+ "## [Problem 1](https://projecteuler.net/problem=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "cell-0091",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 1: Multiples of 3 and 5 0 msec โ 233168 โ
"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_1(N=1000):\n",
+ " \"\"\"Multiples of 3 and 5: Find the sum of all the multiples of 3 or 5 below 1000\"\"\"\n",
+ " return sum(i for i in range(1, N) if i%3 == 0 or i%5 == 0)\n",
+ "\n",
+ "answer(euler_1, 233168)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0001",
+ "metadata": {},
+ "source": [
+ "## [Problem 2](https://projecteuler.net/problem=2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "cell-0092",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 2: Even Fibonacci numbers 0 msec โ 4613732 โ
"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_2(N=4*million):\n",
+ " \"\"\"Even Fibonacci numbers: Find the sum of the even-valued terms in the Fibonacci sequence \n",
+ " whose values do not exceed four million.\"\"\"\n",
+ " return sum(x for x in fibseq(N) if x%2 == 0)\n",
+ "\n",
+ "def fibseq(N) -> Iterable[int]:\n",
+ " \"\"\"Generate the sequence of Fibonacci numbers that are less than N.\"\"\"\n",
+ " a, b = 0, 1\n",
+ " while a < N:\n",
+ " yield a\n",
+ " a, b = b, a + b\n",
+ "\n",
+ "answer(euler_2, 4613732)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0002",
+ "metadata": {},
+ "source": [
+ "## [Problem 3](https://projecteuler.net/problem=3)\n",
+ "\n",
+ "I could have used just `max(sympy.factorint(600851475143))` here, but I did this instead, as shown in [my introductory notebook specifically on this problem](Euler3.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "cell-0093",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 3: Largest prime factor 0 msec โ 6857 โ
"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_3(n=600851475143):\n",
+ " \"\"\"Largest prime factor: What is the largest prime factor of the number 600851475143?\"\"\"\n",
+ " return largest_prime_factor(n)\n",
+ "\n",
+ "def largest_prime_factor(n: int) -> int:\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 divisible by p\n",
+ " return max(p, largest_prime_factor(n // p))\n",
+ " return n # n is prime or 1\n",
+ "\n",
+ "answer(euler_3, 6857)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0003",
+ "metadata": {},
+ "source": [
+ "## [Problem 4](https://projecteuler.net/problem=4)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "cell-0094",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 4: Largest palindrome product 46 msec โ 906609 โ
"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_4():\n",
+ " \"\"\"Largest palindrome product: Find the largest palindrome made from the product of two 3-digit numbers.\"\"\"\n",
+ " three_digit_numbers = range(100, 1000)\n",
+ " products = map(prod, combinations(three_digit_numbers, 2))\n",
+ " return max(filter(palindromic, products))\n",
+ "\n",
+ "answer(euler_4, 906609)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0004",
+ "metadata": {},
+ "source": [
+ "## [Problem 5](https://projecteuler.net/problem=5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "cell-0095",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 5: Smallest multiple 0 msec โ 232792560 โ
"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_5(limit=20):\n",
+ " \"\"\"Smallest multiple: What is the smallest positive number that is evenly divisible by all of 1 to 20?\"\"\"\n",
+ " # I can use the math.lcm (least common multiple) function for this\n",
+ " return lcm(*range(1, limit + 1))\n",
+ "\n",
+ "answer(euler_5, 232792560)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0005",
+ "metadata": {},
+ "source": [
+ "## [Problem 6](https://projecteuler.net/problem=6)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "cell-0096",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 6: Sum square difference 0 msec โ 25164150 โ
"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_6(N=100):\n",
+ " \"\"\"Sum square difference: Find the difference between the sum of the squares of 1..100 and the square of the sum.\"\"\"\n",
+ " sum_of_squares = sum(map(square, ints(1, N)))\n",
+ " square_of_sum = square(sum(ints(1, N)))\n",
+ " return square_of_sum - sum_of_squares\n",
+ "\n",
+ "answer(euler_6, 25164150)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0006",
+ "metadata": {},
+ "source": [
+ "## [Problem 7](https://projecteuler.net/problem=7)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "cell-0097",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 7: 10,001st prime 3 msec โ 104743 โ
"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_7(N=10_001):\n",
+ " \"\"\"10,001st prime: What is the 10,001st prime number (starting from 1)?\"\"\"\n",
+ " # If you think this is cheating, I could have used my `Primes` class, shown at the end of this notebook\n",
+ " return primes[N]\n",
+ "\n",
+ "answer(euler_7, 104743)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0007",
+ "metadata": {},
+ "source": [
+ "## [Problem 8](https://projecteuler.net/problem=8)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "cell-0098",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 8: Largest product in a series 0 msec โ 23514624000 โ
"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_8():\n",
+ " \"\"\"Largest product in a series: Find the 13 adjacent digits that have the greatest product.\"\"\"\n",
+ " substrings = sliding_window(map(int, data_8), 13)\n",
+ " return max(map(prod, substrings))\n",
+ "\n",
+ "data_8 = (\n",
+ " '7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843'\n",
+ " '8586156078911294949545950173795833195285320880551112540698747158523863050715693290963295227443043557'\n",
+ " '6689664895044524452316173185640309871112172238311362229893423380308135336276614282806444486645238749'\n",
+ " '3035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776'\n",
+ " '6572733300105336788122023542180975125454059475224352584907711670556013604839586446706324415722155397'\n",
+ " '5369781797784617406495514929086256932197846862248283972241375657056057490261407972968652414535100474'\n",
+ " '8216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586'\n",
+ " '1786645835912456652947654568284891288314260769004224219022671055626321111109370544217506941658960408'\n",
+ " '0719840385096245544436298123098787992724428490918884580156166097919133875499200524063689912560717606'\n",
+ " '0588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450')\n",
+ "\n",
+ "answer(euler_8, 23514624000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0008",
+ "metadata": {},
+ "source": [
+ "## [Problem 9](https://projecteuler.net/problem=9)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "cell-0099",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 9: Special Pythagorean triplet 13 msec โ 31875000 โ
"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_9(perimeter=1000):\n",
+ " \"\"\"Special Pythagorean triplet: There exists exactly one Pythagorean triplet (a, b, c) for which \n",
+ " the perimeter a + b + c = 1000. Find the product abc.\"\"\"\n",
+ " (a, b, c) = first(pythagorean_triplets(perimeter))\n",
+ " return a * b * c\n",
+ "\n",
+ "def pythagorean_triplets(perimeter: int) -> Iterable[tuple[int, int, int]]:\n",
+ " \"\"\"Generate all Pythagorean triplets with a given perimeter\"\"\"\n",
+ " return ((a, b, c)\n",
+ " for a in range(1, perimeter - 1)\n",
+ " for b in range(a, perimeter)\n",
+ " for c in [perimeter - a - b]\n",
+ " if a ** 2 + b ** 2 == c ** 2)\n",
+ "\n",
+ "answer(euler_9, 31875000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0009",
+ "metadata": {},
+ "source": [
+ "## [Problem 10](https://projecteuler.net/problem=10)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "cell-0100",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 10: Summation of primes 50 msec โ 142913828922 โ
"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_10(N=2*million):\n",
+ " \"\"\"Summation of primes: Find the sum of all the primes below two million.\"\"\"\n",
+ " return sum(primes.primerange(N))\n",
+ "\n",
+ "answer(euler_10, 142913828922)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9bc6058c-b91d-4635-ab8e-36fd1f811061",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 11โ20"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0010",
+ "metadata": {},
+ "source": [
+ "## [Problem 11](https://projecteuler.net/problem=11)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "cell-0101",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 11: Largest product in a matrix 1 msec โ 70600674 โ
"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_11():\n",
+ " \"\"\"Largest product in a matrix: What is the greatest product of four adjacent numbers in the same direction\n",
+ " (up, left, or diagonally) in the 20x20 matrix?\"\"\"\n",
+ " matrix = parse_matrix(data_11)\n",
+ " return max(adjacent_number_product(4, matrix, x, y, dx, dy)\n",
+ " for x in range(20)\n",
+ " for y in range(20)\n",
+ " for (dx, dy) in {(0, 1), (1, 0), (1, 1), (1, -1)})\n",
+ "\n",
+ "def adjacent_number_product(k, matrix, x, y, dx, dy) -> int:\n",
+ " \"\"\"Product of k adjacent numbers starting at matrix[x][y] and going in direction (dx, dy).\"\"\"\n",
+ " try:\n",
+ " return prod(matrix[x + i * dx][y + i * dy] for i in range(k))\n",
+ " except IndexError:\n",
+ " return 1\n",
+ "\n",
+ "data_11 = (\"\"\"08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08\n",
+ "49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00\n",
+ "81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65\n",
+ "52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91\n",
+ "22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80\n",
+ "24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50\n",
+ "32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70\n",
+ "67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21\n",
+ "24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72\n",
+ "21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95\n",
+ "78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92\n",
+ "16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57\n",
+ "86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58\n",
+ "19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40\n",
+ "04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66\n",
+ "88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69\n",
+ "04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36\n",
+ "20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16\n",
+ "20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54\n",
+ "01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48\"\"\")\n",
+ "\n",
+ "answer(euler_11, 70600674)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0011",
+ "metadata": {},
+ "source": [
+ "## [Problem 12](https://projecteuler.net/problem=12)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "cell-0102",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 12: Highly divisible triangular number 155 msec โ 76576500 โ
"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_12(N=500):\n",
+ " \"\"\"Highly divisible triangular number: What is the first triangle number to have over five hundred divisors?\"\"\"\n",
+ " triangle_numbers = map(triangle, integers(start=1))\n",
+ " return first(t for t in triangle_numbers \n",
+ " if len(divisors(t)) > N)\n",
+ "\n",
+ "answer(euler_12, 76576500)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0012",
+ "metadata": {},
+ "source": [
+ "## [Problem 13](https://projecteuler.net/problem=13)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "cell-0103",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 13: Large sum 0 msec โ 5537376230 โ
"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_13(): \n",
+ " \"\"\"Large sum: Work out the first ten digits of the sum of the following one-hundred 50-digit numbers.\"\"\"\n",
+ " total = sum(map(int, data_13.split()))\n",
+ " return int(str(total)[:10])\n",
+ "\n",
+ "data_13 = \"\"\"\n",
+ "37107287533902102798797998220837590246510135740250\n",
+ "46376937677490009712648124896970078050417018260538\n",
+ "74324986199524741059474233309513058123726617309629\n",
+ "91942213363574161572522430563301811072406154908250\n",
+ "23067588207539346171171980310421047513778063246676\n",
+ "89261670696623633820136378418383684178734361726757\n",
+ "28112879812849979408065481931592621691275889832738\n",
+ "44274228917432520321923589422876796487670272189318\n",
+ "47451445736001306439091167216856844588711603153276\n",
+ "70386486105843025439939619828917593665686757934951\n",
+ "62176457141856560629502157223196586755079324193331\n",
+ "64906352462741904929101432445813822663347944758178\n",
+ "92575867718337217661963751590579239728245598838407\n",
+ "58203565325359399008402633568948830189458628227828\n",
+ "80181199384826282014278194139940567587151170094390\n",
+ "35398664372827112653829987240784473053190104293586\n",
+ "86515506006295864861532075273371959191420517255829\n",
+ "71693888707715466499115593487603532921714970056938\n",
+ "54370070576826684624621495650076471787294438377604\n",
+ "53282654108756828443191190634694037855217779295145\n",
+ "36123272525000296071075082563815656710885258350721\n",
+ "45876576172410976447339110607218265236877223636045\n",
+ "17423706905851860660448207621209813287860733969412\n",
+ "81142660418086830619328460811191061556940512689692\n",
+ "51934325451728388641918047049293215058642563049483\n",
+ "62467221648435076201727918039944693004732956340691\n",
+ "15732444386908125794514089057706229429197107928209\n",
+ "55037687525678773091862540744969844508330393682126\n",
+ "18336384825330154686196124348767681297534375946515\n",
+ "80386287592878490201521685554828717201219257766954\n",
+ "78182833757993103614740356856449095527097864797581\n",
+ "16726320100436897842553539920931837441497806860984\n",
+ "48403098129077791799088218795327364475675590848030\n",
+ "87086987551392711854517078544161852424320693150332\n",
+ "59959406895756536782107074926966537676326235447210\n",
+ "69793950679652694742597709739166693763042633987085\n",
+ "41052684708299085211399427365734116182760315001271\n",
+ "65378607361501080857009149939512557028198746004375\n",
+ "35829035317434717326932123578154982629742552737307\n",
+ "94953759765105305946966067683156574377167401875275\n",
+ "88902802571733229619176668713819931811048770190271\n",
+ "25267680276078003013678680992525463401061632866526\n",
+ "36270218540497705585629946580636237993140746255962\n",
+ "24074486908231174977792365466257246923322810917141\n",
+ "91430288197103288597806669760892938638285025333403\n",
+ "34413065578016127815921815005561868836468420090470\n",
+ "23053081172816430487623791969842487255036638784583\n",
+ "11487696932154902810424020138335124462181441773470\n",
+ "63783299490636259666498587618221225225512486764533\n",
+ "67720186971698544312419572409913959008952310058822\n",
+ "95548255300263520781532296796249481641953868218774\n",
+ "76085327132285723110424803456124867697064507995236\n",
+ "37774242535411291684276865538926205024910326572967\n",
+ "23701913275725675285653248258265463092207058596522\n",
+ "29798860272258331913126375147341994889534765745501\n",
+ "18495701454879288984856827726077713721403798879715\n",
+ "38298203783031473527721580348144513491373226651381\n",
+ "34829543829199918180278916522431027392251122869539\n",
+ "40957953066405232632538044100059654939159879593635\n",
+ "29746152185502371307642255121183693803580388584903\n",
+ "41698116222072977186158236678424689157993532961922\n",
+ "62467957194401269043877107275048102390895523597457\n",
+ "23189706772547915061505504953922979530901129967519\n",
+ "86188088225875314529584099251203829009407770775672\n",
+ "11306739708304724483816533873502340845647058077308\n",
+ "82959174767140363198008187129011875491310547126581\n",
+ "97623331044818386269515456334926366572897563400500\n",
+ "42846280183517070527831839425882145521227251250327\n",
+ "55121603546981200581762165212827652751691296897789\n",
+ "32238195734329339946437501907836945765883352399886\n",
+ "75506164965184775180738168837861091527357929701337\n",
+ "62177842752192623401942399639168044983993173312731\n",
+ "32924185707147349566916674687634660915035914677504\n",
+ "99518671430235219628894890102423325116913619626622\n",
+ "73267460800591547471830798392868535206946944540724\n",
+ "76841822524674417161514036427982273348055556214818\n",
+ "97142617910342598647204516893989422179826088076852\n",
+ "87783646182799346313767754307809363333018982642090\n",
+ "10848802521674670883215120185883543223812876952786\n",
+ "71329612474782464538636993009049310363619763878039\n",
+ "62184073572399794223406235393808339651327408011116\n",
+ "66627891981488087797941876876144230030984490851411\n",
+ "60661826293682836764744779239180335110989069790714\n",
+ "85786944089552990653640447425576083659976645795096\n",
+ "66024396409905389607120198219976047599490197230297\n",
+ "64913982680032973156037120041377903785566085089252\n",
+ "16730939319872750275468906903707539413042652315011\n",
+ "94809377245048795150954100921645863754710598436791\n",
+ "78639167021187492431995700641917969777599028300699\n",
+ "15368713711936614952811305876380278410754449733078\n",
+ "40789923115535562561142322423255033685442488917353\n",
+ "44889911501440648020369068063960672322193204149535\n",
+ "41503128880339536053299340368006977710650566631954\n",
+ "81234880673210146739058568557934581403627822703280\n",
+ "82616570773948327592232845941706525094512325230608\n",
+ "22918802058777319719839450180888072429661980811197\n",
+ "77158542502016545090413245809786882778948721859617\n",
+ "72107838435069186155435662884062257473692284509516\n",
+ "20849603980134001723930671666823555245252804609722\n",
+ "53503534226472524250874054075591789781264330331690\"\"\"\n",
+ "\n",
+ "answer(euler_13, 5537376230)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0013",
+ "metadata": {},
+ "source": [
+ "## [Problem 14](https://projecteuler.net/problem=14)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "cell-0104",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 14: Longest Collatz sequence 465 msec โ 837799 โ
"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_14(limit=million):\n",
+ " \"\"\"Longest Collatz sequence: Which starting number, under one million, produces the longest Collatz sequence chain?\"\"\"\n",
+ " return max(range(1, limit), key=collatz_len)\n",
+ "\n",
+ "@cache\n",
+ "def collatz_len(n) -> int:\n",
+ " \"\"\"The length of the Collatz sequence chain starting at n.\"\"\"\n",
+ " return 1 + (0 if n == 1 else\n",
+ " collatz_len(n // 2) if n % 2 == 0 else\n",
+ " collatz_len(3 * n + 1))\n",
+ "\n",
+ "answer(euler_14, 837799)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0014",
+ "metadata": {},
+ "source": [
+ "## [Problem 15](https://projecteuler.net/problem=15)\n",
+ "\n",
+ "For this problem I could do a recursive walk through the grid, with a `@cache` decorator for speed. There will be several other problems like that. But an easier way is to notice that the path will be 40 steps, a permutation of 20 right and 20 down steps, and by the permutation formula the answer is (2*N*)! / (*N*!)2."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "cell-0105",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 15: Lattice paths 0 msec โ 137846528820 โ
"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_15(N=20):\n",
+ " \"\"\"Lattice paths: How many routes are there through a 20x20 grid?\n",
+ " Start in top left, take steps either right or down, end in bottom right.\"\"\"\n",
+ " return factorial(2 * N) // factorial(N) ** 2\n",
+ "\n",
+ "answer(euler_15, 137846528820)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0015",
+ "metadata": {},
+ "source": [
+ "## [Problem 16](https://projecteuler.net/problem=16)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "cell-0106",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 16: Power digit sum 0 msec โ 1366 โ
"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_16(N=1000):\n",
+ " \"\"\"Power digit sum: What is the sum of the digits of the number 2**1000?\"\"\"\n",
+ " return sum(digitlist(2 ** N))\n",
+ "\n",
+ "answer(euler_16, 1366)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0016",
+ "metadata": {},
+ "source": [
+ "## [Problem 17](https://projecteuler.net/problem=17)\n",
+ "\n",
+ "I did this in Common Lisp, which has a `~r` [format directive](https://en.wikipedia.org/wiki/Format_(Common_Lisp)) that prints the argument as an English number:\n",
+ "\n",
+ " (loop for i from 1 to 1000 summing (count-if #'alpha-char-p (format nil \"~r\" i)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "cell-0107",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 17: Number letter counts 0 msec โ 21124 โ
"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_17():\n",
+ " \"\"\"Number letter counts: If all the numbers from 1 to 1000 inclusive were written out in words, \n",
+ " how many letters would be used?\"\"\"\n",
+ " return 21124\n",
+ "\n",
+ "answer(euler_17, 21124)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0017",
+ "metadata": {},
+ "source": [
+ "## [Problem 18](https://projecteuler.net/problem=18)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "cell-0108",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 18: Maximum path sum I 0 msec โ 1074 โ
"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "data_18 = \"\"\"\n",
+ "75\n",
+ "95 64\n",
+ "17 47 82\n",
+ "18 35 87 10\n",
+ "20 04 82 47 65\n",
+ "19 01 23 75 03 34\n",
+ "88 02 77 73 07 63 67\n",
+ "99 65 04 28 06 16 70 92\n",
+ "41 41 26 56 83 40 80 70 33\n",
+ "41 48 72 33 47 32 37 16 94 29\n",
+ "53 71 44 65 25 43 91 52 97 51 14\n",
+ "70 11 33 28 77 73 17 78 39 68 17 57\n",
+ "91 71 52 38 17 14 91 43 58 50 27 29 48\n",
+ "63 66 04 68 89 53 67 30 73 16 69 87 40 31\n",
+ "04 62 98 27 23 09 70 98 73 93 38 53 60 04 23\"\"\"\n",
+ "\n",
+ "def euler_18(data=data_18):\n",
+ " \"\"\"Maximum path sum I: Find the maximum total from top to bottom of the triangle\n",
+ " (by starting at the top and moving to adjacent numbers on the row below).\"\"\"\n",
+ " # See also # 67\n",
+ " return maxroute_in_triangle(parse_matrix(data))\n",
+ "\n",
+ "def maxroute_in_triangle(triangle) -> int:\n",
+ " \"\"\"The route through a triangle from top to bottom that maximizes the sum of the path.\"\"\"\n",
+ " \n",
+ " bottom = len(triangle) - 1\n",
+ " \n",
+ " @cache\n",
+ " def cost(r, c) -> int:\n",
+ " \"\"\"Find sum of maximum route through triangle, starting from row n, col c\"\"\"\n",
+ " if r == bottom:\n",
+ " return triangle[r][c]\n",
+ " else:\n",
+ " return triangle[r][c] + max(cost(r + 1, c), cost(r + 1, c + 1))\n",
+ " \n",
+ " return cost(0, 0)\n",
+ "\n",
+ "\n",
+ "\n",
+ "answer(euler_18, 1074)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0018",
+ "metadata": {},
+ "source": [
+ "## [Problem 19](https://projecteuler.net/problem=19)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "cell-0109",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 19: Counting Sundays 6 msec โ 171 โ
"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_19():\n",
+ " \"\"\"Counting Sundays: How many Sundays fell on the first of the month during the twentieth century \n",
+ " (1 Jan 1901 to 31 Dec 2000)?\"\"\"\n",
+ " import datetime\n",
+ " Sunday = 6\n",
+ " start = datetime.date(1901, 1, 1).toordinal()\n",
+ " days = [datetime.date.fromordinal(x) for x in range(start, start + 36600)]\n",
+ " return quantify(d.day == 1 and d.weekday() == Sunday and 1901 <= d.year <= 2000\n",
+ " for d in days)\n",
+ "\n",
+ "answer(euler_19, 171)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0019",
+ "metadata": {},
+ "source": [
+ "## [Problem 20](https://projecteuler.net/problem=20)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "cell-0110",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 20: Factorial digit sum 0 msec โ 648 โ
"
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_20(N=100):\n",
+ " \"\"\"Factorial digit sum: Find the sum of the digits in the number 100!\"\"\"\n",
+ " return digitsum(factorial(N))\n",
+ "\n",
+ "answer(euler_20, 648)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bb355ced-aa9d-4ab8-b914-cde7b1b32bab",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 21โ30"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0020",
+ "metadata": {},
+ "source": [
+ "## [Problem 21](https://projecteuler.net/problem=21)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "cell-0111",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 21: Amicable numbers 108 msec โ 31626 โ
"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_21(N=10000):\n",
+ " \"\"\"Amicable numbers: evaluate the sum of all the amicable numbers under 10000.\n",
+ " Numbers are amicable if the sum of the proper divisors of a equals b, and vice-versa.\"\"\"\n",
+ " return sum(filter(is_amicable, range(1, N)))\n",
+ "\n",
+ "def is_amicable(a: int) -> bool:\n",
+ " \"\"\"Is a amicable with some other integer b? That is, is there some b distinct from a\n",
+ " such that d(a) = b and d(b) = a, where d computes the sum of the proper divsors.\"\"\"\n",
+ " # Note: if a == b, that's called a \"perfect\" number.\n",
+ " b = sum_proper_divisors(a)\n",
+ " return a != b and a == sum_proper_divisors(b)\n",
+ "\n",
+ "def sum_proper_divisors(n) -> int: \n",
+ " \"\"\"The sum of all the divisors of n, except for n itself.\"\"\"\n",
+ " return sum(divisors(n, proper=True))\n",
+ "\n",
+ "answer(euler_21, 31626)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0021",
+ "metadata": {},
+ "source": [
+ "## [Problem 22](https://projecteuler.net/problem=22)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "cell-0112",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 22: Names scores 12 msec โ 871198282 โ
"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_22(path=Path('p022_names.txt')):\n",
+ " \"\"\"Names scores: What is the total of all the name scores in the file names.txt?\n",
+ " First sort the names into alphabetical order, \n",
+ " Then the name score is the sum of the alphabetical values of the letters of the name,\n",
+ " multiplied by the position of the name in alphabetical order.\"\"\"\n",
+ " names = literal_eval(read(path))\n",
+ " alphabetical_order = enumerate(sorted(names), 1)\n",
+ " return sum(alphabetical_value_sum(name) * position for (position, name) in alphabetical_order)\n",
+ "\n",
+ "def alphabetical_value_sum(name: str) -> int:\n",
+ " \"\"\"Sum of the numerical score of each letter in name: 1 for 'A', 2 for 'B', ... .\"\"\"\n",
+ " return sum(1 + ord(c) - ord('A') for c in name)\n",
+ "\n",
+ "answer(euler_22, 871198282)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0022",
+ "metadata": {},
+ "source": [
+ "## [Problem 23](https://projecteuler.net/problem=23)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "id": "cell-0113",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 23: Non-abundant sums 472 msec โ 4179871 โ
"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_23(limit=28123):\n",
+ " \"\"\"Non-abundant sums: Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers.\"\"\"\n",
+ " # A number n is abundant if the sum of its proper divisors is greater than n.\n",
+ " # We were given: All integers > 28123 can be written as the sum of 2 abundant numbers.\n",
+ " # I'll make a set of all abundants up to that limit,\n",
+ " # and then look for numbers `n` that can be formed by the sum of two abundants: \n",
+ " # `a`, which we know is abundant, and `n - a`, which we check to see if it is in `abundants`.\n",
+ " abundants = {n for n in ints(1, limit) if sum_proper_divisors(n) > n}\n",
+ " return sum(n for n in ints(1, limit)\n",
+ " if not any(n - a in abundants \n",
+ " for a in abundants))\n",
+ "\n",
+ "answer(euler_23, 4179871)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0023",
+ "metadata": {},
+ "source": [
+ "## [Problem 24](https://projecteuler.net/problem=24)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "id": "cell-0114",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 24: Lexicographic permutations 6 msec โ 2783915460 โ
"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_24(N=million):\n",
+ " \"\"\"Lexicographic permutations: What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9.\"\"\"\n",
+ " perm = nth(permutations('0123456789'), N - 1)\n",
+ " return int(concat(perm)) \n",
+ "\n",
+ "answer(euler_24, 2783915460)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0024",
+ "metadata": {},
+ "source": [
+ "## [Problem 25](https://projecteuler.net/problem=25)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "id": "cell-0115",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 25: 1000-digit Fibonacci number 16 msec โ 4782 โ
"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_25(N=1000):\n",
+ " \"\"\"1000-digit Fibonacci number: What (index number) is the first term in the Fibonacci sequence to contain 1000 digits?\"\"\"\n",
+ " return first(i for (i, n) in enumerate(fibseq(inf)) if len(str(n)) == N)\n",
+ "\n",
+ "answer(euler_25, 4782)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0025",
+ "metadata": {},
+ "source": [
+ "## [Problem 26](https://projecteuler.net/problem=26)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "cell-0116",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 26: Reciprocal cycles 270 msec โ 983 โ
"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_26(N=1000):\n",
+ " \"\"\"Reciprocal cycles: Find the value of d < 1000 for which 1/d contains the longest recurring cycle in its decimal fraction part.\"\"\"\n",
+ " # From wikipedia.org/wiki/Repeating_decimal : \n",
+ " # \"The period of the repeating decimal of 1/d is equal to the multiplicative order of 10 modulo d.\"\n",
+ " # The multiplicative order of 10 mod d is the smallest k such that 10**k = 1 (mod d)\n",
+ " return max(range(1, N), key=cycle_length)\n",
+ "\n",
+ "def cycle_length(d: int) -> int:\n",
+ " \"\"\"The length of the recurring cycle in the decimal representation of 1/d.\"\"\"\n",
+ " return first(k for k in ints(1, d) if (10 ** k) % d == 1)\n",
+ "\n",
+ "answer(euler_26, 983)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0026",
+ "metadata": {},
+ "source": [
+ "## [Problem 27](https://projecteuler.net/problem=27)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "id": "cell-0117",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 27: Quadratic primes 234 msec โ -59231 โ
"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_27(limit=1000):\n",
+ " \"\"\"Quadratic primes: Find the product of the coefficients, a and b, \n",
+ " for the quadratic expression (n**2 + a*n + b), starting with n = 0,\n",
+ " that produces the maximum number of primes for consecutive values of n.\"\"\"\n",
+ " a_values = ints(-limit, limit) # I guess at a limit for `a`\n",
+ " b_values = primerange(limit) # `b` must be prime (so that we get a prime when n=0)\n",
+ " coefficients = crossproduct(a_values, b_values)\n",
+ " a, b = max(coefficients, key=count_consecutive_primes)\n",
+ " return a * b\n",
+ "\n",
+ "def count_consecutive_primes(coefficients: tuple[int, int]) -> int:\n",
+ " \"\"\"How many values of `n` starting at 0 give a prime for n**2 + a*n + b?\"\"\"\n",
+ " a, b = coefficients\n",
+ " for n in integers(0):\n",
+ " if not isprime(n * n + a * n + b):\n",
+ " return n\n",
+ "\n",
+ "answer(euler_27, -59231)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0027",
+ "metadata": {},
+ "source": [
+ "## [Problem 28](https://projecteuler.net/problem=28)\n",
+ "\n",
+ "This problem involves spirals that look like this:\n",
+ "\n",
+ " 21 22 23 24 25 ...\n",
+ " 20 7 8 9 10\n",
+ " 19 6 1 2 11\n",
+ " 18 5 4 3 12\n",
+ " 17 16 15 14 13"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "cell-0118",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 28: Number spiral diagonals 0 msec โ 669171001 โ
"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_28(N=1001):\n",
+ " \"\"\"Number spiral diagonals: What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral?\"\"\"\n",
+ " return sum(spiral_diagonals(N))\n",
+ "\n",
+ "def spiral_diagonals(size) -> list[int]:\n",
+ " \"\"\"Return all the numbers that are in the diagonals of the spiral.\n",
+ " Build up the spiral one layer at a time, keeping track of the corners (diagonals).\"\"\"\n",
+ " delta = 2\n",
+ " i = 1\n",
+ " corners = [1]\n",
+ " for layer in range(size // 2):\n",
+ " for corner in range(4):\n",
+ " i += delta\n",
+ " corners.append(i)\n",
+ " delta += 2\n",
+ " return corners\n",
+ "\n",
+ "assert spiral_diagonals(5) == [1, 3, 5, 7, 9, 13, 17, 21, 25]\n",
+ "\n",
+ "answer(euler_28, 669171001)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0028",
+ "metadata": {},
+ "source": [
+ "## [Problem 29](https://projecteuler.net/problem=29)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "id": "cell-0119",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 29: Distinct powers 2 msec โ 9183 โ
"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_29(N=100):\n",
+ " \"\"\"Distinct powers: How many distinct terms are in the sequence generated by a**b for 2 <= a <= 100 and 2 <= b <= 100?\"\"\"\n",
+ " return len({a ** b for a in ints(2, N) for b in ints(2, N)})\n",
+ "\n",
+ "answer(euler_29, 9183)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0029",
+ "metadata": {},
+ "source": [
+ "## [Problem 30](https://projecteuler.net/problem=30)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "id": "cell-0120",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 30: Digit fifth powers 190 msec โ 443839 โ
"
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_30():\n",
+ " \"\"\"Digit fifth powers: Find the sum of all the numbers that can be written as the sum of fifth powers of their digits.\"\"\"\n",
+ " # A d-digit number has sum of 5th powers satisfying d*(1**5) <= sum <= d*(9**5).\n",
+ " # For d=7, the maximum sum is 7*9**5 = 413,343, a 6-digit number, so we need only go up to 6 digits: 6 * 9 ** 5\n",
+ " # The problem says 1 doesn't count, so start at 2.\n",
+ " return sum(filter(equals_sum_5th_powers_of_digits, ints(2, 6 * 9 ** 5 + 1)))\n",
+ "\n",
+ "def equals_sum_5th_powers_of_digits(n: int) -> bool: \n",
+ " \"\"\"Does n equal the sum of the 5th powers of its digits?\"\"\"\n",
+ " return n == sum(d ** 5 for d in digitlist(n))\n",
+ "\n",
+ "answer(euler_30, 443839)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "129b9e81-7940-45e1-91f4-100feefd43fc",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 31โ40"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0030",
+ "metadata": {},
+ "source": [
+ "## [Problem 31](https://projecteuler.net/problem=31)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "id": "cell-0121",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 31: Coin sums 0 msec โ 73682 โ
"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_31(N=200):\n",
+ " \"\"\"Coin sums: How many different ways can 200 be made using the specified denominations of coins?\"\"\"\n",
+ " return coin_sums(N)\n",
+ "\n",
+ "@cache\n",
+ "def coin_sums(n, coins=(200, 100, 50, 20, 10, 5, 2, 1)):\n",
+ " \"\"\"In how many ways can we make add up to n using the coins given?\"\"\"\n",
+ " return (1 if (n == 0) else # one way: \"nothing will come of nothing\" - King Lear\n",
+ " 0 if (not coins or n < 0) else # no way: can't make something with nothing\n",
+ " coin_sums(n - coins[0], coins) + # multiple ways: with and without using first coin\n",
+ " coin_sums(n, coins[1:]))\n",
+ "\n",
+ "answer(euler_31, 73682)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0031",
+ "metadata": {},
+ "source": [
+ "## [Problem 32](https://projecteuler.net/problem=32)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "cell-0122",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 32: Pandigital products 74 msec โ 45228 โ
"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_32(limit=10_000):\n",
+ " \"\"\"Pandigital products: Find the sum of all products whose multiplicand * multiplier = product identity\n",
+ " can be written as a 1 through 9 pandigital.\"\"\"\n",
+ " ## We need consider products, only up to 4 digits: a 5 digit number can't be the product of 4 other digits. \n",
+ " return sum(filter(has_pandigital_product_identity, range(1, limit)))\n",
+ "\n",
+ "def has_pandigital_product_identity(p: int) -> bool:\n",
+ " \"\"\"Given a product, p, is there a divisor d, such that {d, p/d, p} forms a pandigital set?\"\"\"\n",
+ " return any(pandigital(concat([d, p // d, p]))\n",
+ " for d in divisors(p))\n",
+ "\n",
+ "def pandigital(digit_str, digitset=set('123456789')):\n",
+ " \"\"\"A digit string is pandigital if it is a permutation of the digits.\n",
+ " Some authors include 0 in the digits, but Project Euler does not.\"\"\"\n",
+ " return len(digit_str) == len(digitset) and set(digit_str) == digitset\n",
+ "\n",
+ "answer(euler_32, 45228)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0032",
+ "metadata": {},
+ "source": [
+ "## [Problem 33](https://projecteuler.net/problem=33)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "cell-0123",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 33: Digit canceling fractions 3 msec โ 100 โ
"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_33():\n",
+ " \"\"\"Digit canceling fractions: 49/98 = 4/8 is curious; looks like you cancel the 9s.\n",
+ " There are 4 non-trivial fractions of this form, less than 1 in value, with two digits\n",
+ " in both denominator and numerator. Multiply the 4 fractions and give the denominator.\"\"\"\n",
+ " # We want a/b with 10 <= a < b < 99\n",
+ " fractions = {Fraction(a, b) for a in ints(10, 99) for b in ints(a + 1, 99) \n",
+ " if cancellable_fraction(a,b)}\n",
+ " assert len(fractions) == 4\n",
+ " return prod(fractions)._denominator\n",
+ "\n",
+ "def cancellable_fraction(a,b):\n",
+ " \"\"\"Can you cancel a digit from both a and b to get an equal fraction, like 49/98 => 4/9?\"\"\"\n",
+ " ## Canceling a '0', as in 20/30 = 2/3 is trivial; doesn't count\n",
+ " A, B = str(a), str(b)\n",
+ " cancelable_digits = set(A + B) - {'0'}\n",
+ " return any(d for d in cancelable_digits\n",
+ " if a * int(B.replace(d,'',1)) == b * int(A.replace(d,'',1)))\n",
+ "\n",
+ "answer(euler_33, 100)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0033",
+ "metadata": {},
+ "source": [
+ "## [Problem 34](https://projecteuler.net/problem=34)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "id": "cell-0124",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 34: Digit factorials 468 msec โ 40730 โ
"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_34():\n",
+ " \"\"\"Digit factorials: Find the sum of all numbers which are equal to the sum of the factorial of their digits.\"\"\"\n",
+ " # Note: According to the problem, one digit numbers don't count.\n",
+ " # A 7-digit number has factorial sum at most `limit` = 7 * 9! = 2,540,160 \n",
+ " # So any 7-digit number > that, or any 8+ digit number must be > its factorial sum.\n",
+ " fact = {str(d): factorial(d) for d in range(10)}.get # Cached factorial(digit: str) function\n",
+ " limit = 7 * factorial(9)\n",
+ " return sum(n for n in ints(10, limit)\n",
+ " if n == sum(map(fact, str(n))))\n",
+ " \n",
+ "answer(euler_34, 40730)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0034",
+ "metadata": {},
+ "source": [
+ "## [Problem 35](https://projecteuler.net/problem=35)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "id": "cell-0125",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 35: Circular primes 382 msec โ 55 โ
"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_35(N=million):\n",
+ " \"\"\"Circular primes: How many of the primes below one million are circular primes?\"\"\"\n",
+ " return quantify(primes.primerange(N), is_circular_prime)\n",
+ "\n",
+ "def is_circular_prime(p) -> bool:\n",
+ " \"\"\"Is p a circular prime? That is, are all its rotations of digits prime?\"\"\"\n",
+ " return all(map(isprime, digit_rotations(p)))\n",
+ "\n",
+ "def digit_rotations(n):\n",
+ " \"\"\"All digit rotations of n, e.g. digit_rotations(123)) == [123, 231, 312]\"\"\"\n",
+ " s = str(n)\n",
+ " return [int(s[i:]+s[:i]) for i in range(len(s))]\n",
+ "\n",
+ "answer(euler_35, 55)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0035",
+ "metadata": {},
+ "source": [
+ "## [Problem 36](https://projecteuler.net/problem=36)\n",
+ "\n",
+ "Iterating through the integers and checking for palindromic numbers is *O*(*n*). I could speed that up to *O*(โ*n*) by enumerating through integers `i`, converting `i` to a string `s`, and yielding `s + s[::-1]` (even digit-length palindromes) and `s + s[-2::-1]` (odd digit-length palindromes). But this is fast enough."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "id": "cell-0126",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 36: Double-base palindromes 75 msec โ 872187 โ
"
+ ]
+ },
+ "execution_count": 39,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_36(N=million):\n",
+ " \"\"\"Double-base palindromes: Find the sum of all numbers, less than one million, \n",
+ " which are palindromic in both base 10 and base 2.\"\"\"\n",
+ " return sum(i for i in range(N)\n",
+ " if palindromic(i) and palindromic(f'{i:b}'))\n",
+ "\n",
+ "answer(euler_36, 872187)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0036",
+ "metadata": {},
+ "source": [
+ "## [Problem 37](https://projecteuler.net/problem=37)\n",
+ "\n",
+ "I covered truncatable primes in-depth in [another notebook](TruncatablePrimes.ipynb). \n",
+ "\n",
+ "This problem states \"NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes.\" I disagree with this and I get support from the definitive source, [OEIS](https://oeis.org), both [here](https://oeis.org/A024785) and [here](https://oeis.org/A024770), but in order to get the right answer, I eliminate one-digit primes in `is_left_truncatable`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "id": "cell-0127",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 37: Truncatable primes 0 msec โ 748317 โ
"
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_37():\n",
+ " \"\"\"Truncatable primes: Find the sum of the only 11 primes that are truncatable from left to right and right to left.\"\"\"\n",
+ " return sum(filter(is_left_truncatable, right_truncatable_primes()))\n",
+ "\n",
+ "def right_truncatable_primes(start=[2, 3, 5, 7]) -> list[int]:\n",
+ " \"\"\"All right-truncatable primes, in ascending order, starting with the given primes.\"\"\"\n",
+ " # Only consider (1, 3, 7, 9) as the digit placed on the right; placing any other digit forms a composite number\n",
+ " new_numbers = (10 * p + d for p in start for d in (1, 3, 7, 9))\n",
+ " new_primes = [n for n in new_numbers if isprime(n)]\n",
+ " if new_primes:\n",
+ " return start + right_truncatable_primes(new_primes)\n",
+ " else:\n",
+ " return start\n",
+ "\n",
+ "def is_left_truncatable(p: int) -> bool:\n",
+ " \"\"\"Can you drop a digit from the left and get a prime?\"\"\"\n",
+ " s = str(p)\n",
+ " return p >= 10 and all(isprime(int(s[i:])) for i in range(1, len(s)))\n",
+ "\n",
+ "answer(euler_37, 748317)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0037",
+ "metadata": {},
+ "source": [
+ "## [Problem 38](https://projecteuler.net/problem=38)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "id": "cell-0128",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 38: Pandigital multiples 43 msec โ 932718654 โ
"
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_38():\n",
+ " \"\"\"Pandigital multiples: What is the largest pandigital 9-digit number that can be formed as the concatenated\n",
+ " product of an integer with the list (1,2, ... , n) where n > 1? For example:\n",
+ " 192 * 1 = 192; 192 * 2 = 384; 192 * 3 = 576, and 192384576 is pandigital.\n",
+ " Thus each candidate product is of the form total = 'i*1' 'i*2' ... 'i*n' , for some i and n.\"\"\"\n",
+ " candidates = (concatenated_prod(i, ints(1, n))\n",
+ " for i in ints(1, 9999)\n",
+ " for n in ints(2,9))\n",
+ " return int(max(filter(pandigital, candidates)))\n",
+ "\n",
+ "def concatenated_prod(n, multipliers) -> str:\n",
+ " \"\"\"E.g. concatenated_prod(192, (1, 2, 3)) = '192384576' because 192*1=192, 192*2=384, 192*3=576\"\"\"\n",
+ " return concat(n * m for m in multipliers)\n",
+ "\n",
+ "answer(euler_38, 932718654)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0038",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## [Problem 39](https://projecteuler.net/problem=39)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "cell-0129",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 39: Integer right triangles 8 msec โ 840 โ
"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_39(P=1000):\n",
+ " \"\"\"Integer right triangles: Which value of the perimeter p <= 1000 has the max number of integral right triangles with perimeter p?\"\"\"\n",
+ " perimeters = map(sum, integral_right_triangles(P))\n",
+ " [(p, count)] = Counter(perimeters).most_common(1)\n",
+ " return p\n",
+ "\n",
+ "def integral_right_triangles(P: int) -> Iterable[tuple[int, int, int]]:\n",
+ " \"\"\"All (a, b, c) tuples that form a right triangle, with a <= b < c, and perimeter a + b + c < P.\"\"\"\n",
+ " return ((a, b, int(c))\n",
+ " for a, b in combinations(ints(1, P // 2), 2) # Neither a nor b may be more than half the perimeter\n",
+ " if (c:= sqrt(a ** 2 + b ** 2)).is_integer() and a + b + c <= P)\n",
+ "\n",
+ "assert list(integral_right_triangles(30)) == [(3, 4, 5), (5, 12, 13), (6, 8, 10)]\n",
+ "\n",
+ "answer(euler_39, 840)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0039",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## [Problem 40](https://projecteuler.net/problem=40)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "cell-0130",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 40: Champernowne's constant 39 msec โ 210 โ
"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_40():\n",
+ " \"\"\"Champernowne's constant: An irrational decimal fraction is created by concatenating the positive integers:\n",
+ " 0.123456789101112131415161718192021...\n",
+ " If d[n] is the nth digit, find d[1] * d[10] * d[100] * d[1000] * d[10000] * d[100000] * d[1000000].\"\"\"\n",
+ " # Just concatenate the positive integers. How many? million/5 should do it, because each number averages over 5 digits.\n",
+ " d = digitlist(concat(range(million // 5)))\n",
+ " return prod(d[10 ** i] for i in range(7))\n",
+ "\n",
+ "answer(euler_40, 210)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3cd9740a-1960-480e-8856-1b9b1a9385f8",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 41โ50"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0040",
+ "metadata": {},
+ "source": [
+ "## [Problem 41](https://projecteuler.net/problem=41)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "id": "cell-0131",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 41: Pandigital prime 1 msec โ 7652413 โ
"
+ ]
+ },
+ "execution_count": 44,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_41():\n",
+ " \"\"\"Pandigital prime: What is the largest n-digit pandigital prime?\"\"\"\n",
+ " # 8 and 9 digit numbers are out, since 1+2+3+4+5+6+7+8 and 1+2+3+4+5+6+7+8+9 are divisible by 9,\n",
+ " # and thus any permutation of them would not be a prime.\n",
+ " # I assume there will be a 7-digit pandigital prime.\n",
+ " pandigitals = (int(concat(digits)) for digits in permutations('1234567'))\n",
+ " return first(filter(isprime, sorted(pandigitals, reverse=True)))\n",
+ "\n",
+ "answer(euler_41, 7652413)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0041",
+ "metadata": {},
+ "source": [
+ "## [Problem 42](https://projecteuler.net/problem=42)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "id": "cell-0132",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 42: Coded triangle numbers 3 msec โ 162 โ
"
+ ]
+ },
+ "execution_count": 45,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_42(words=None):\n",
+ " \"\"\"Coded triangle numbers: How many words are triangle words \n",
+ " (their alphabetical value (see euler_22) is a triangle number)?\"\"\"\n",
+ " words = words or literal_eval(read(Path('p042_words.txt')))\n",
+ " triangle_numbers = set(map(triangle, range(1, 100)))\n",
+ " return quantify(alphabetical_value_sum(w) in triangle_numbers \n",
+ " for w in words)\n",
+ "\n",
+ "answer(euler_42, 162)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0042",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## [Problem 43](https://projecteuler.net/problem=43)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "id": "9ceaf594-a7f4-4ab1-8774-030276f05abb",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 43: Sub-string divisibility 12 msec โ 16695334890 โ
"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_43(digits=set(digits)):\n",
+ " \"\"\"Sub-string divisibility: Find the sum of all 0-to-9 pandigital numbers with the substring divisibility property:\n",
+ " 3-digit substrings of n are each divisible by the respective first 7 primes; that is,\n",
+ " d2d3d4 is divisible by 2; d3d4d5 is divisible by 3; ... d8d9d10 is divisible by 17.\n",
+ " I could try all 10! permutations of the digits, but to speed things up, I first generate the tail 4 digits;\n",
+ " few of them will have the property of being divisible by 13 and 17. Of those that are, I can then\n",
+ " generate all possible head 5 digits and test for divisibility by 2, 3, 5, 7, 11\"\"\"\n",
+ " return sum(int(concat(head + tail))\n",
+ " for tail in permutations(digits, 4)\n",
+ " if substring_divisibility(tail, (13, 17), 0) \n",
+ " for head in permutations(digits - set(tail))\n",
+ " if substring_divisibility(head + tail, (2, 3, 5, 7, 11), 1))\n",
+ "\n",
+ "def substring_divisibility(n: list[str], primes: tuple[int], start: int) -> bool: \n",
+ " \"\"\"For each p in `primes`, is the corresponding 3-digit substring of `n` divisible by p?\n",
+ " By \"corresponding\", I mean starting at `start` for the first p, `start+1` for the second, etc.\"\"\"\n",
+ " return all(int(concat(n[i:i+3])) % p == 0\n",
+ " for (i, p) in enumerate(primes, start))\n",
+ "\n",
+ "answer(euler_43, 16695334890)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0043",
+ "metadata": {},
+ "source": [
+ "## [Problem 44](https://projecteuler.net/problem=44)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "id": "cell-0134",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 44: Pentagon numbers 110 msec โ 5482660 โ
"
+ ]
+ },
+ "execution_count": 47,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_44(N=3000):\n",
+ " \"\"\"Pentagon numbers: Find the pair of pentagonal numbers, Pj and Pk, for which their sum and difference is pentagonal\n",
+ " and D = |Pk - Pj| is minimised; what is the value of D?\"\"\"\n",
+ " # I had no idea how many pentagonals to try; I guessed N=3000.\n",
+ " pentagonals = set(map(pentagonal, range(1, N)))\n",
+ " return min(abs(pk - pj)\n",
+ " for pj, pk in combinations(pentagonals, 2)\n",
+ " if pk + pj in pentagonals \n",
+ " and pk - pj in pentagonals)\n",
+ "\n",
+ "answer(euler_44, 5482660)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0044",
+ "metadata": {},
+ "source": [
+ "## [Problem 45](https://projecteuler.net/problem=45)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "id": "cell-0135",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 45: Triangular/pentagonal/hexagonal 18 msec โ 1533776805 โ
"
+ ]
+ },
+ "execution_count": 48,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_45(N=100000):\n",
+ " \"\"\"Triangular/pentagonal/hexagonal: Find the next triangle number after 40755 that is also pentagonal and hexagonal.\"\"\"\n",
+ " triangles = (triangle(n) for n in integers(40755))\n",
+ " pentagonals = {pentagonal(n) for n in range(N)}\n",
+ " hexagonals = {hexagonal(n) for n in range(N)}\n",
+ " return first(t for t in triangles\n",
+ " if t in pentagonals and t in hexagonals)\n",
+ "\n",
+ "answer(euler_45, 1533776805)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0045",
+ "metadata": {},
+ "source": [
+ "## [Problem 46](https://projecteuler.net/problem=46)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "cell-0136",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 46: Goldbach's other conjecture 4 msec โ 5777 โ
"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_46():\n",
+ " \"\"\"Goldbach's other conjecture: What is the smallest odd composite that can't be written as the sum \n",
+ " of a prime and twice a square?\"\"\"\n",
+ " odd_composites = (i for i in integers(start=3, step=2) if not isprime(i))\n",
+ " return first(filter(not_sum_of_prime_and_twice_square, odd_composites))\n",
+ " \n",
+ "def not_sum_of_prime_and_twice_square(n: int) -> bool:\n",
+ " \"\"\"Is there no way to express n as the sum of a prime and twice a square?\"\"\"\n",
+ " # If n = p + 2 * i ** 2, for some prime p and some i, then n - 2 * i ** 2 must be prime\n",
+ " return not any(isprime(n - 2 * i ** 2) \n",
+ " for i in ints(1, n))\n",
+ "\n",
+ "answer(euler_46, 5777)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0046",
+ "metadata": {},
+ "source": [
+ "## [Problem 47](https://projecteuler.net/problem=47)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "id": "cell-0137",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 47: Distinct primes factors 272 msec โ 134043 โ
"
+ ]
+ },
+ "execution_count": 50,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_47(N=4):\n",
+ " \"\"\"Distinct primes factors: Find the first N=4 consecutive integers to have exactly 4 distinct primes factors.\n",
+ " What is the first of these 4 numbers?\"\"\"\n",
+ " consecutive = 0\n",
+ " for i in integers():\n",
+ " if consecutive == N:\n",
+ " return i - N\n",
+ " elif len(factorint(i)) == N:\n",
+ " consecutive += 1\n",
+ " else:\n",
+ " consecutive = 0\n",
+ "\n",
+ "assert euler_47(2) == 14 and euler_47(3) == 644 # As specified in the problem description\n",
+ "\n",
+ "answer(euler_47, 134043)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0047",
+ "metadata": {},
+ "source": [
+ "## [Problem 48](https://projecteuler.net/problem=48)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "cell-0138",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 48: Self powers 1 msec โ 9110846700 โ
"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_48(N=1000, M=10**10):\n",
+ " \"\"\"Self powers: Find the last ten digits of the (sum of the) series, 1**1 + 2**2 + 3**3 + ... + 1000**1000.\"\"\"\n",
+ " # To deal only with the last 10 digits, operate modulo M = 10**10\n",
+ " return sum(pow(i, i, mod=M) for i in ints(1, N)) % M\n",
+ "\n",
+ "answer(euler_48, 9110846700)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0048",
+ "metadata": {},
+ "source": [
+ "## [Problem 49](https://projecteuler.net/problem=49)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "id": "cell-0139",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 49: Prime permutations 0 msec โ 296962999629 โ
"
+ ]
+ },
+ "execution_count": 52,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_49(delta=3330):\n",
+ " \"\"\"Prime permutations: The arithmetic sequence, 1487, 4817, 8147, in which each of the terms increases by 3330, is unusual\n",
+ " in two ways: (i) each of the three terms are prime, and, (ii) each of the 4-digit numbers are permutations of one another.\n",
+ " There is one other 4-digit (increasing) sequence with this property.\n",
+ " What 12-digit number do you form by concatenating the three terms in this sequence?\"\"\"\n",
+ " four_digit_primes_over_1487 = primes.primerange(1487 + 1, 9999)\n",
+ " return first_true(prime_permutation(p, p + delta, p + 2 * delta)\n",
+ " for p in four_digit_primes_over_1487)\n",
+ "\n",
+ "def prime_permutation(a, b, c) -> int | None:\n",
+ " \"\"\"If a, b, c are all primes and permutations of each other then return their concatenation.\"\"\"\n",
+ " if isprime(b) and isprime(c) and is_permutation(a, b) and is_permutation(b, c):\n",
+ " return int(concat((a, b, c)))\n",
+ " else:\n",
+ " return None\n",
+ "\n",
+ "answer(euler_49, 2969_6299_9629)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0049",
+ "metadata": {},
+ "source": [
+ "## [Problem 50](https://projecteuler.net/problem=50)\n",
+ "\n",
+ "For this problem, I'll iterate through posssible value of the number of consecutive primes, `n`, starting with a guess of \n",
+ "1000 consecutive primes and moving downward. For each possible value of `n` I'll check if `sum_consecutive_primes` can find a prime that is the sum of `n` consecutive primes.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "id": "cell-0140",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 50: Consecutive prime sum 3 msec โ 997651 โ
"
+ ]
+ },
+ "execution_count": 53,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_50(limit=million, N=1000):\n",
+ " \"\"\"Consecutive prime sum: Which prime, below one-million, can be written as the sum of the most consecutive primes?\"\"\"\n",
+ " # Enumerate possible lengths, n, longest first, and return the first prime that is the sum of n consecutive primes.\n",
+ " prime_sequence = list(primerange(limit))\n",
+ " return first_true(sum_consecutive_primes(prime_sequence, n, limit)\n",
+ " for n in reversed(range(N)))\n",
+ "\n",
+ "def sum_consecutive_primes(sequence, n, limit) -> int | None:\n",
+ " \"\"\"If some n-element subsequence of `sequence` sums to a prime p, then return p. Otherwise None.\"\"\" \n",
+ " total = sum(sequence[:n])\n",
+ " for i in range(n, len(sequence) + 1):\n",
+ " if total > limit: \n",
+ " return None\n",
+ " elif total in primes:\n",
+ " return total\n",
+ " else:\n",
+ " total += sequence[i] - sequence[i - n]\n",
+ " return None\n",
+ "\n",
+ "answer(euler_50, 997651)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ecf6c24d-e5d8-424e-ae4b-d96dce2fe520",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 51โ60"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0050",
+ "metadata": {},
+ "source": [
+ "## [Problem 51](https://projecteuler.net/problem=51)\n",
+ "\n",
+ "The problem was a bit ambiguous, but I take the instructions to mean that you pick some digit *d* that appears in the number, and replace every instance of the digit with a chosen replacement digit, e.g. replace all the '0's in 56003 with a '1' to get 56113. But don't replace the first digit with a 0; that's not allowed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "id": "e9bdfc4d-4fd0-4daf-bf17-745cbb063e71",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 51: Prime digit replacements 315 msec โ 121313 โ
"
+ ]
+ },
+ "execution_count": 54,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_51(length=8):\n",
+ " \"\"\"Prime digit replacements: Find the smallest prime which, by replacing part of the number \n",
+ " (not necessarily adjacent digits) with the same digit, is part of an eight-prime-value family.\"\"\"\n",
+ " # For each prime p (in order), check if it forms a prime family with at least length=8 members.\n",
+ " return first(p for p in primes if prime_family(p, length))\n",
+ "\n",
+ "def prime_family(p, length) -> list[int]:\n",
+ " \"\"\"Can some substitution for a subset of p's digits form a prime family with at least `length` primes? \n",
+ " If so, return the family.\"\"\"\n",
+ " s = str(p)\n",
+ " for d in set(s):\n",
+ " family = [int(s.replace(d, d2)) \n",
+ " for d2 in digits\n",
+ " if not (d2 == '0' and d == s[0])] # Don't replace leading digit with a '0'\n",
+ " prime_family = [n for n in family if isprime(n)]\n",
+ " if len(prime_family) >= length:\n",
+ " return prime_family\n",
+ "\n",
+ "\n",
+ "answer(euler_51, 121313)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "id": "26fa821f-5693-49a9-83bf-2e0c17223e67",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Here's how prime_digit_family works:\n",
+ "assert prime_family(121313, 8) == [121313, 222323, 323333, 424343, 525353, 626363, 828383, 929393]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0051",
+ "metadata": {},
+ "source": [
+ "## [Problem 52](https://projecteuler.net/problem=52)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "id": "cell-0142",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 52: Permuted multiples 93 msec โ 142857 โ
"
+ ]
+ },
+ "execution_count": 56,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_52():\n",
+ " \"\"\"Permuted multiples: find the smallest positive integer, x, such that x, 2x, 3x, 4x, 5x, and 6x, contain the same digits.\"\"\"\n",
+ " s = sorted_characters # Use `s` as an abbreviation for the sorted_characters utility function\n",
+ " return first(x for x in integers(start=1)\n",
+ " if s(x) == s(2*x) == s(3*x) == s(4*x) == s(5*x) == s(6*x))\n",
+ "\n",
+ "answer(euler_52, 142857)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0052",
+ "metadata": {},
+ "source": [
+ "## [Problem 53](https://projecteuler.net/problem=53)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "id": "cell-0143",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 53: Combinatoric selections 0 msec โ 4075 โ
"
+ ]
+ },
+ "execution_count": 57,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_53():\n",
+ " \"\"\"Combinatoric selections: How many values of n C r, for 1 <= n <= 100, are greater than a million?\n",
+ " They need not be distinct.\"\"\"\n",
+ " return quantify(comb(n,r) > million\n",
+ " for n in ints(1,100) \n",
+ " for r in ints(1,n))\n",
+ "\n",
+ "answer(euler_53, 4075)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0053",
+ "metadata": {},
+ "source": [
+ "## [Problem 54](https://projecteuler.net/problem=54)\n",
+ "\n",
+ "We decide which poker hand wins by computing the `poker_value` for each hand, and comparing the values with `>`. The value is a tuple, where the first element is an integer denoting the category (8 for straight flush, 4 for straight, etc.) and the second element is a list of list of \"kickers,\" the exact rank of each card, organized with the most important ones first.\n",
+ "\n",
+ "|variable|value|explanation|\n",
+ "|---|---|---|\n",
+ "|hand | ['8S', '5C', '8D', '5D', '5S'] | example hand of cards: 8โ 5โฃ 8โฆ 5โฆ 5โ |\n",
+ "|ranks| [8, 5, 8, 5, 5] | ranks of the five cards|\n",
+ "|suits | {'S', 'C', 'D'} | set of the suits of the cards |\n",
+ "|flush | False| not a flush|\n",
+ "|straight|False|not a straight|\n",
+ "| kind |[[], [], [8], [5], []]|kind[2] = 8 (pair) and kind[3] = 5 (3-of-a-kind)|\n",
+ "| category | 6 | full house: a 3-of-a-kind and a 2-of-a-kind (pair)|\n",
+ "| kickers | [[], [5], [8], [], []]|the 5 (3-of-a-kind) is more important than the 8 (pair)|\n",
+ "| return |(6, [[], [5], [8], [], []]|major category and minor tiebreakers|"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "id": "86c25a3b-ca8c-4154-9ba9-2dbece46ad23",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 54: Poker hands 8 msec โ 376 โ
"
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_54(path=Path(\"p054_poker.txt\")):\n",
+ " \"\"\"Poker hands: In how many hands of poker does Player 1 win in the file poker.txt, \n",
+ " which has 2 hands of 5 cards on each line?\"\"\"\n",
+ " lines = map(str.split, read(path).splitlines())\n",
+ " return quantify(poker_value(line[:5]) > poker_value(line[5:])\n",
+ " for line in lines)\n",
+ "\n",
+ "def poker_value(hand: list[str]) -> tuple[int, list]:\n",
+ " \"\"\"Return a value for a poker hand. The value can be used to compare to other poker hands,\n",
+ " and consists of a category value (0 to 8) followed by kickers (tiebreakers).\"\"\"\n",
+ " # kind[k] is a list of the ranks that have k-of-a-kind\n",
+ " ranks = ['..23456789TJQKA'.index(r) for r,s in hand]\n",
+ " suits = {s for r,s in hand}\n",
+ " flush = len(suits) == 1\n",
+ " straight = (max(ranks) - min(ranks)) == 4 and len(set(ranks)) == 5\n",
+ " straight = all(r in ranks for r in ints(min(ranks), max(ranks)))\n",
+ " kind = [[r for r in range(14, 2, -1) if ranks.count(r) == i] for i in range(5)]\n",
+ " category = (8 if straight and flush else\n",
+ " 7 if kind[4] else\n",
+ " 6 if kind[3] and kind[2] else\n",
+ " 5 if flush else\n",
+ " 4 if straight else\n",
+ " 3 if kind[3] else\n",
+ " 2 if len(kind[2]) == 2 else\n",
+ " 1 if kind[2] else\n",
+ " 0)\n",
+ " kickers = kind[::-1]\n",
+ " return (category, kickers)\n",
+ "\n",
+ "answer(euler_54, 376)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0054",
+ "metadata": {},
+ "source": [
+ "## [Problem 55](https://projecteuler.net/problem=55)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "id": "cell-0145",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 55: Lychrel numbers 10 msec โ 249 โ
"
+ ]
+ },
+ "execution_count": 59,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_55(N=10000):\n",
+ " \"\"\"Lychrel numbers: How many Lychrel numbers are there below ten-thousand?\"\"\"\n",
+ " return quantify(range(1, N), is_lychrel)\n",
+ "\n",
+ "def is_lychrel(n, iterations=50) -> bool:\n",
+ " \"\"\"A number that never forms a palindrome through the 'reverse and add' \n",
+ " process is called a Lychrel number. Details: If we take 47, reverse and add, \n",
+ " 47 + 74 = 121, which is palindromic. If we start with 349, it takes three iterations: \n",
+ " 349 + 943 = 1292; 1292 + 2921 = 4213; 4213 + 3124 = 7337. A number is Lychrel\n",
+ " if it never forms a palindrome after the specified number of iterations (default 50).\"\"\"\n",
+ " for _ in range(iterations):\n",
+ " n = n + int(str(n)[::-1]) # Reverse and add\n",
+ " if palindromic(n):\n",
+ " return False\n",
+ " return True\n",
+ "\n",
+ "answer(euler_55, 249)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0055",
+ "metadata": {},
+ "source": [
+ "## [Problem 56](https://projecteuler.net/problem=56)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "id": "cell-0146",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 56: Powerful digit sum 44 msec โ 972 โ
"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_56(consider=range(100)):\n",
+ " \"\"\"Powerful digit sum: Considering natural numbers of the form, a**b, where a, b < 100, \n",
+ " what is the maximum sum of the digits?\"\"\"\n",
+ " return max(digitsum(a**b) for a in consider for b in consider)\n",
+ "\n",
+ "answer(euler_56, 972)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0056",
+ "metadata": {},
+ "source": [
+ "## [Problem 57](https://projecteuler.net/problem=57)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "id": "cell-0147",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 57: Square root convergents 5 msec โ 153 โ
"
+ ]
+ },
+ "execution_count": 61,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_57(N=1000):\n",
+ " \"\"\"Square root convergents: In the first one-thousand expansions of sqrt(2), how many fractions contain\n",
+ " a numerator with more digits than denominator?\"\"\"\n",
+ " return quantify(len(str(x._numerator)) > len(str(x._denominator))\n",
+ " for x in islice(root2(), N))\n",
+ "\n",
+ "def root2() -> Fraction:\n",
+ " \"\"\"Yield the successive expansions of sqrt(2).\"\"\"\n",
+ " x = Fraction(1, 2)\n",
+ " while True:\n",
+ " yield 1 + x\n",
+ " x = Fraction(1, 2 + x)\n",
+ "\n",
+ "answer(euler_57, 153)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0057",
+ "metadata": {},
+ "source": [
+ "## [Problem 58](https://projecteuler.net/problem=58)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "id": "cell-0148",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 58: Spiral primes 43 msec โ 26241 โ
"
+ ]
+ },
+ "execution_count": 62,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_58(proportion=0.1):\n",
+ " \"\"\"Spiral primes: Starting with 1 and spiralling anticlockwise in the following way, \n",
+ " a square spiral with side length 7 is formed.\n",
+ " 37 36 35 34 33 32 31\n",
+ " 38 17 16 15 14 13 30\n",
+ " 39 18 5 4 3 12 29\n",
+ " 40 19 6 1 2 11 28\n",
+ " 41 20 7 8 9 10 27\n",
+ " 42 21 22 23 24 25 26\n",
+ " 43 44 45 46 47 48 49 ...\n",
+ " If this process is continued, what is the side length of the square spiral for which \n",
+ " the ratio of primes along both diagonals first falls below 10%?\"\"\"\n",
+ " # The diagonal numbers are:\n",
+ " # 9, 7, 5, 3 for side = 3\n",
+ " # 25, 21, 17, 13 for side = 5\n",
+ " # s^2, s^2-(s-1), s^2-2*(s-1), s^2-3*(s-1) for side = s\n",
+ " # Of course, s^2 can never be prime, so only check the others.\n",
+ " nprimes = 0\n",
+ " for s in integers(start=3, step=2):\n",
+ " nprimes += quantify(isprime(s ** 2 - i * (s - 1))\n",
+ " for i in (1, 2, 3))\n",
+ " if nprimes / (2 * s - 1.0) < proportion:\n",
+ " return s\n",
+ "\n",
+ "answer(euler_58, 26241)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0058",
+ "metadata": {},
+ "source": [
+ "## [Problem 59](https://projecteuler.net/problem=59)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "id": "cell-0149",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 59: XOR decryption 771 msec โ 107359 โ
"
+ ]
+ },
+ "execution_count": 63,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_59(path=Path(\"p059_cipher.txt\")):\n",
+ " \"\"\"XOR decryption: Using cipher1.txt, a file containing the encrypted ASCII codes, and the knowledge that the plain text \n",
+ " must contain common English words, and has been XOR-encoded with a pad of three lowercase letters, decode the message \n",
+ " and find the sum of the ASCII values in the original text.\"\"\"\n",
+ " text = literal_eval(read(path))\n",
+ " def decrypt(text, pad) -> int:\n",
+ " \"\"\"XOR the text with the pad (cycling through the pad if it runs out).\"\"\"\n",
+ " return [t ^ p for (t, p) in zip(text, cycle(pad))]\n",
+ " def score(text) -> int:\n",
+ " \"\"\"How many common words can you find in text?\"\"\"\n",
+ " strtext = concat(map(chr, text))\n",
+ " return len(re.findall(' the | of | and | to | in | a | is ', strtext, re.I))\n",
+ " lower = range(ord('a'), ord('z')+1)\n",
+ " candidates = [decrypt(text, pad) for pad in crossproduct(lower, lower, lower)]\n",
+ " return sum(max(candidates, key=score))\n",
+ "\n",
+ "answer(euler_59, 107359)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0059",
+ "metadata": {},
+ "source": [
+ "## [Problem 60](https://projecteuler.net/problem=60)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "id": "db4e2538-850c-4b4b-9576-3ee3fdd6b12d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 60: Prime pair sets 875 msec โ 26033 โ
"
+ ]
+ },
+ "execution_count": 64,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import warnings\n",
+ "warnings.filterwarnings('default') # Show all warnings\n",
+ " \n",
+ "def euler_60(N=5, limit=9000) -> int:\n",
+ " \"\"\"Prime pair sets: Find the lowest sum for a set of five primes for which any two concatenate to produce another prime.\"\"\"\n",
+ " # I guessed that limit=9000 would be high enough, and as it turned out, it worked.\n",
+ " compatible = list(primerange(2, limit))\n",
+ " return min(map(sum, cliques(N, [], compatible)))\n",
+ "\n",
+ "def cliques(N, clique: list, compatible: list) -> Iterable[[int]]:\n",
+ " \"\"\"Generate cliques of N primes where each pair concatenates to a prime with all the others, in either order.\n",
+ " `clique` is the starting clique, and `compatible` are primes that successfully concatenate with the whole clique.\"\"\"\n",
+ " # I could make this faster by bailing out \n",
+ " if len(clique) == N:\n",
+ " yield clique\n",
+ " else:\n",
+ " for i, p in enumerate(compatible):\n",
+ " new_compatible = [q for q in compatible[i + 1:] if concat_to_prime(str(p), str(q))]\n",
+ " yield from cliques(N, clique + [p], new_compatible)\n",
+ "\n",
+ "def concat_to_prime(p: str, q: str) -> bool:\n",
+ " \"\"\"Do p and q concatenate to form a prime, in both orders?\"\"\"\n",
+ " return isprime(int(p + q)) and isprime(int(q + p))\n",
+ "\n",
+ "answer(euler_60, 26033)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b60b5ea5-5279-49d9-8e3b-80bfd6135bf0",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 61โ70"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0060",
+ "metadata": {},
+ "source": [
+ "## [Problem 61](https://projecteuler.net/problem=61)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "id": "cell-0151",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 61: Cyclical figurate numbers 3 msec โ 28684 โ
"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_61(N=6):\n",
+ " \"\"\"Cyclical figurate numbers: Find the sum of the only ordered set of six cyclic \n",
+ " 4-digit numbers for which each polygonal type: triangle, square, pentagonal, \n",
+ " hexagonal, heptagonal, and octagonal, is represented by a different number in the set.\n",
+ " Here m and n are cyclical numbers if n ends with the same two digits that m starts with.\"\"\"\n",
+ " polygonal_functions = [triangle, square, pentagonal, hexagonal, heptagonal, octagonal]\n",
+ " Psets = {frozenset(fn(i) for i in range(200) if 1000 <= fn(i) <= 9999)\n",
+ " for fn in polygonal_functions}\n",
+ " return sum(find_cyclical_numbers(N, Psets, []))\n",
+ "\n",
+ "def find_cyclical_numbers(N, Psets, nums) -> list[int]|None:\n",
+ " \"\"\"Extend `nums` into an ordered cyclic chain of N more numbers, each taken from a\n",
+ " distinct set in `Psets`, or return False if no such chain exists.\n",
+ " Backtracking search: at each step choose an unused set P and a number n in P that is\n",
+ " cyclical with the previous number (n's first two digits equal the previous number's last\n",
+ " two). The last number placed (N == 1) must also be cyclical back to nums[0], closing the\n",
+ " ring; the first number placed (nums == []) has no predecessor to match.\"\"\"\n",
+ " if N == 0:\n",
+ " return nums\n",
+ " else:\n",
+ " # Continue the list 'nums' with a new cyclical number from a new Pset\n",
+ " return first_true(find_cyclical_numbers(N-1, Psets - {P}, nums + [n])\n",
+ " for P in Psets\n",
+ " for n in P\n",
+ " if (nums == []) or cyclical_numbers(nums[-1], n)\n",
+ " if N > 1 or cyclical_numbers(n, nums[0]))\n",
+ "\n",
+ "def cyclical_numbers(n, m) -> bool:\n",
+ " \"\"\"True if n ends with the same two digits that m starts with.\"\"\"\n",
+ " return m // 100 == n % 100\n",
+ "\n",
+ "answer(euler_61, 28684)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0061",
+ "metadata": {},
+ "source": [
+ "## [Problem 62](https://projecteuler.net/problem=62)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "id": "cell-0152",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 62: Cubic permutations 6 msec โ 127035954683 โ
"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_62(N=5):\n",
+ " \"\"\"Cubic permutations: Find the smallest cube for which exactly five permutations of its digits are cube.\"\"\"\n",
+ " D = defaultdict(list)\n",
+ " for i in integers(start=1):\n",
+ " key = sorted_characters(i**3)\n",
+ " D[key].append(i)\n",
+ " if len(D[key]) == N:\n",
+ " return min(D[key])**3\n",
+ "\n",
+ "answer(euler_62, 127035954683)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0062",
+ "metadata": {},
+ "source": [
+ "## [Problem 63](https://projecteuler.net/problem=63)\n",
+ "\n",
+ "In the expoential *b**n*, if *b* โฅ 10, then *b**n* will always have more than *n* digits. Similarly, 9100 has only 96 digits, so the exponent must be less than that. We can exhaustively try all combinations of base and exponent in these ranes.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "id": "cell-0153",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 63: Powerful digit counts 0 msec โ 49 โ
"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_63():\n",
+ " \"\"\"Powerful digit counts: How many n-digit positive integers exist which are also an nth power?\"\"\"\n",
+ " return quantify(len(str(b ** n)) == n\n",
+ " for b in range(1, 10) \n",
+ " for n in range(1, 100))\n",
+ "\n",
+ "answer(euler_63, 49)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0063",
+ "metadata": {},
+ "source": [
+ "## [Problem 67](https://projecteuler.net/problem=67)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "id": "cell-0154",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 67: Maximum path sum II 4 msec โ 7273 โ
"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_67(path=Path(\"p067_triangle.txt\")):\n",
+ " \"\"\"Maximum path sum II: Find the maximum total from top to bottom in the file, \n",
+ " which contains a triangle with one-hundred rows.\"\"\"\n",
+ " # See euler_18 where `maxroute_in_triangle` is defined. \n",
+ " # I used @cache there, so it is fast enough to use here.\n",
+ " return maxroute_in_triangle(parse_matrix(read(path)))\n",
+ "\n",
+ "answer(euler_67, 7273)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0064",
+ "metadata": {},
+ "source": [
+ "## [Problem 68](https://projecteuler.net/problem=68)\n",
+ "\n",
+ "Let *A, B, C, D, E* be the outer ring, with *A* the lowest one, and *a, b, c, d, e* be the inner ring, with *A* connected to *a*, etc.. The 10 must be in the outer ring (and thus counted only once) to get 16-digit strings. First try all permutations of 5 integers for the outer ring; there are 30240 of them, and eliminate the 1/2 that do not contain 10 and the 4/5 for which *A* is not the minimum. Then try all 120 permutations of the remaining digits for the inner ring, checking to see if the magic condition holds."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "id": "714da3ca-21aa-4fa0-8afd-f40bf13d38df",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 68: Magic 5-gon ring 23 msec โ 6531031914842725 โ
"
+ ]
+ },
+ "execution_count": 69,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_68():\n",
+ " \"\"\"Magic 5-gon ring: What is the maximum 16-digit string for a 'magic' 5-gon ring?\"\"\"\n",
+ " return int(max(magic5gons()))\n",
+ "\n",
+ "def magic5gons() -> Iterable[str]:\n",
+ " \"\"\"Yield all 16-digit strings that correspond to magic 5-gons.\"\"\"\n",
+ " nodes = set(range(1, 11))\n",
+ " for (A, B, C, D, E) in permutations(nodes, 5):\n",
+ " outer = {A, B, C, D, E}\n",
+ " if A == min(outer) and 10 in outer:\n",
+ " for (a, b, c, d, e) in permutations(nodes - outer, 5):\n",
+ " if A+a+b == B+b+c == C+c+d == D+d+e == E+e+a:\n",
+ " yield concat((A, a, b, B, b, c, C, c, d, D, d, e, E, e, a))\n",
+ "\n",
+ "answer(euler_68, 6531031914842725)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0065",
+ "metadata": {},
+ "source": [
+ "## [Problem 69](https://projecteuler.net/problem=69)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 70,
+ "id": "cell-0156",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 69: Totient maximum 108 msec โ 510510 โ
"
+ ]
+ },
+ "execution_count": 70,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_69(N=million):\n",
+ " \"\"\"Totient maximum: Find the value of n <= 1,000,000 for which n/phi(n) is a maximum.\"\"\"\n",
+ " # Could be max((float(n)/phi(n), n) for n in range(million)), but too slow.\n",
+ " # Consider: n is a product of prime factors.\n",
+ " # Repeating a prime factor of n won't help.\n",
+ " # I guess that n will be the product of the first M primes, for some M.\n",
+ " primes = list(primerange(N))\n",
+ " compatible = [primes[:M] for M in range(1, 20)]\n",
+ " return prod(max((factors for factors in compatible if prod(factors) < N),\n",
+ " key=phi_ratio))\n",
+ "\n",
+ "def phi_ratio(factors):\n",
+ " \"\"\"Given n as a list of factors, return the n/phi(n) ratio, along with n, phi(n) and factors.\"\"\"\n",
+ " n = prod(factors)\n",
+ " phi_n = quantify(all(gcd(i, f)==1 for f in factors) for i in range(1,n))\n",
+ " return n / phi_n, n, phi_n, factors\n",
+ "\n",
+ "answer(euler_69, 510510)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9846b305-0a88-497c-843c-75db326e8be3",
+ "metadata": {},
+ "source": [
+ "## [Problem 70](https://projecteuler.net/problem=70)\n",
+ "\n",
+ "Eulerโs Totient function, ฯ(*n*), is the number of positive integers less than or equal to *n* which are relatively prime to *n*. We're asked to minimize *n*/ฯ(*n*), and that ratio is minimized when *n* is a prime, because ฯ(*p*) = *p* - 1 when *p* is prime. But *p* and *p* - 1 can never be permutations of each other; they have different digits. So instead let's guess that *n* is the product of two primes, *n* = pq. In that case, ฯ(*p*ร*q*) = (*p* - 1) ร (*q* - 1). To maximize that product, let's further guess that *p* and *q* are within, say, a factor of 2 of the square root of the limit. That makes it all easy and fast:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 71,
+ "id": "61177e1b-a397-4085-8a73-80c563b1e06b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 70: Totient Permutation 31 msec โ 8319823 โ
"
+ ]
+ },
+ "execution_count": 71,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_70(limit=10*million):\n",
+ " \"\"\"Totient Permutation: Find the value of n, 1 < n < 10^7, for which ฯ(n) is a permutation of n and \n",
+ " the ratio n/ฯ(n) is a minimum. ฯ(n) is the number of positive integers <= n which are relatively prime to n.\"\"\"\n",
+ " guess = sqrt(limit)\n",
+ " primes = primerange(guess // 2, guess * 2)\n",
+ " _, n = min([ratio(p, q), p * q]\n",
+ " for (p, q) in combinations(primes, 2)\n",
+ " if p * q < limit and is_permutation(p * q, ฯ(p, q)))\n",
+ " return n\n",
+ "\n",
+ "def ฯ(p, q) -> int: return (p - 1) * (q - 1)\n",
+ "def ratio(p, q) -> float: return (p * q) / ฯ(p, q)\n",
+ "\n",
+ "answer(euler_70, 8319823)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f42ed7b-b061-405e-9938-61715320fd5b",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 71โ80"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0066",
+ "metadata": {},
+ "source": [
+ "## [Problem 71](https://projecteuler.net/problem=71)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 72,
+ "id": "cell-0157",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 71: Ordered fractions 0 msec โ 428570 โ
"
+ ]
+ },
+ "execution_count": 72,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_71(limit=million):\n",
+ " \"\"\"Ordered fractions: By listing the set of reduced proper fractions n/d for d <= 1,000,000 in ascending order,\n",
+ " find the numerator of the fraction immediately to the left of 3/7.\"\"\"\n",
+ " # This is a guess, but it worked:\n",
+ " return 3 * (limit // 7) - 1\n",
+ "\n",
+ "answer(euler_71, 428570)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0067",
+ "metadata": {},
+ "source": [
+ "## [Problem 72](https://projecteuler.net/problem=72)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 73,
+ "id": "6b34a0be-0de1-46dc-8622-67de22376b93",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 72: Counting fractions 379 msec โ 303963552391 โ
"
+ ]
+ },
+ "execution_count": 73,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_72(dlimit=million):\n",
+ " \"\"\"Counting fractions: Consider the fraction n/d, with n < d. \n",
+ " If their highest common factor is 1, it is a reduced proper fraction.\n",
+ " How many reduced proper fractions with d < million?\"\"\"\n",
+ " # Finally, Euler gets to help solve an Euler problem!\n",
+ " # We use Euler's totient function, ฯ(n),\n",
+ " # described at https://en.wikipedia.org/wiki/Euler%27s_totient_function\n",
+ " ฯ = list(range(dlimit + 1))\n",
+ " # Sieve to compute ฯ(n)\n",
+ " for i in range(2, dlimit + 1):\n",
+ " if ฯ[i] == i: # i is prime\n",
+ " for j in range(i, dlimit + 1, i):\n",
+ " ฯ[j] -= ฯ[j] // i\n",
+ " # Sum of all totients (starting from 2)\n",
+ " return sum(ฯ[2:])\n",
+ "\n",
+ "answer(euler_72, 303_963_552_391)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0068",
+ "metadata": {},
+ "source": [
+ "## [Problem 73](https://projecteuler.net/problem=73)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 74,
+ "id": "cell-0158",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 73: Counting fractions in a range 756 msec โ 7295372 โ
"
+ ]
+ },
+ "execution_count": 74,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_73(D=12000):\n",
+ " \"\"\"Counting fractions in a range: How many fractions n/d lie between 1/3 and 1/2 \n",
+ " in the sorted set of reduced fractions for d <= 12,000?\"\"\"\n",
+ " # Try n/d for all n from 1 up to D/2,\n",
+ " # and for all d above 2n and below 3n, and also <= 12,000\n",
+ " # only count n/d if it is a reduced fraction: gcd(n, d) == 1\n",
+ " return quantify(gcd(n, d) == 1\n",
+ " for n in range(1, D // 2 + 1)\n",
+ " for d in range(2 * n + 1, min(3 * n, D + 1)))\n",
+ "\n",
+ "answer(euler_73, 7295372)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0069",
+ "metadata": {},
+ "source": [
+ "## [Problem 74](https://projecteuler.net/problem=74)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 75,
+ "id": "cell-0159",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 74: Digit factorial chains 536 msec โ 402 โ
"
+ ]
+ },
+ "execution_count": 75,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_74(N=million):\n",
+ " \"\"\"Digit factorial chains: Consider the sequence 169 => 1!+6!+9!=36301 => 3!+6!+3!+0!+1!=1454 => 1!+4!+5!+4!=169.\n",
+ " This chain has length three (169, 36301, 1454) before it repeats (169).\n",
+ " How many chains, with a starting number below one million, contain exactly sixty non-repeating terms?\"\"\"\n",
+ " factorials = {str(d): factorial(d) for d in range(10)}\n",
+ " lengths = {} # map of {sorted-digits: length}\n",
+ "\n",
+ " def chain_length(n) -> int:\n",
+ " \"\"\"Length of the digit-factorial-chain starting at n.\"\"\"\n",
+ " chain = set()\n",
+ " key = sorted_characters(n)\n",
+ " if key in lengths: \n",
+ " return len(chain) + lengths[key]\n",
+ " while True:\n",
+ " chain.add(n)\n",
+ " n = sum(factorials[d] for d in str(n))\n",
+ " if n in chain:\n",
+ " lengths[key] = len(chain)\n",
+ " return len(chain)\n",
+ "\n",
+ " return quantify(chain_length(n) == 60 for n in range(1, N))\n",
+ "\n",
+ "answer(euler_74, 402)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0071",
+ "metadata": {},
+ "source": [
+ "## [Problem 75](https://projecteuler.net/problem=75)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 76,
+ "id": "cell-0161",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 75: Singular integer right triangles 99 msec โ 161667 โ
"
+ ]
+ },
+ "execution_count": 76,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_75(L=1500000):\n",
+ " \"\"\"Singular integer right triangles: for how many values of perimeter L <= 1,500,000 can exactly one\n",
+ " integer sided right angle (Pythagorean) triangle be formed?\"\"\"\n",
+ " # This is like _39, but for 1.5 million, not just 1000, so we need to be more efficient.\n",
+ " # integers http://en.wikipedia.org/wiki/Pythagorean_triple we get some help:\n",
+ " # Given m,n, m>n, m-n is odd, m,n are coprime, then\n",
+ " # a, b, c = m**2-n**2, 2*m*n, m**2 + n**2 is a Pythagorean triplet\n",
+ " M = isqrt(L)\n",
+ " ntriangles = [0]*L # ntriangles[p] is the number of right triangles of perimeter p\n",
+ " for m in range(1, M, 2): # m odd\n",
+ " for n in range(2, M - m, 2): # n even\n",
+ " if gcd(m, n) == 1:\n",
+ " # Not sure which of m,n is bigger, so use abs\n",
+ " perim = abs(m**2-n**2) + 2*m*n + m**2 + n**2\n",
+ " for p in range(perim, L, perim):\n",
+ " ntriangles[p]+=1\n",
+ " return ntriangles.count(1)\n",
+ "\n",
+ "answer(euler_75, 161667)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0072",
+ "metadata": {},
+ "source": [
+ "## [Problem 76](https://projecteuler.net/problem=76)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 77,
+ "id": "cell-0162",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 76: Counting summations 1 msec โ 190569291 โ
"
+ ]
+ },
+ "execution_count": 77,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_76(N=100) -> int:\n",
+ " \"\"\"Counting summations: How many different ways can one hundred be written as a sum of at least two positive integers?\"\"\"\n",
+ " # We have \"- 1\" because `npartitions` counts a partition with one element, [N], but the problem doesn't want that one.\n",
+ " return npartitions(N, 1) - 1\n",
+ "\n",
+ "@cache\n",
+ "def npartitions(n, k=1) -> int:\n",
+ " \"\"\"In how many ways can we partition n, using only integers of size k or larger.\"\"\"\n",
+ " # Note npartitions is similar to coin_sums. \n",
+ " return (1 if (n == 0 or n == k) else\n",
+ " 0 if (n < 0 or n < k) else\n",
+ " npartitions(n, k+1) + npartitions(n-k, k))\n",
+ "\n",
+ "answer(euler_76, 190569291)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0073",
+ "metadata": {},
+ "source": [
+ "## [Problem 77](https://projecteuler.net/problem=77)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 78,
+ "id": "cell-0163",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 77: Prime summations 1 msec โ 71 โ
"
+ ]
+ },
+ "execution_count": 78,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_77(N=5000):\n",
+ " \"\"\"Prime summations: What is the first integer that can be written as the sum of primes in over five thousand different ways?\"\"\"\n",
+ " return first(n for n in integers(start=2) \n",
+ " if prime_sum_ways(n) > N)\n",
+ "\n",
+ "@cache\n",
+ "def prime_sum_ways(n, i=1) -> int:\n",
+ " \"\"\"In how many ways can we make the sum of n, using only primes >= prime(i)?\"\"\"\n",
+ " p = prime(i)\n",
+ " return (1 if (n == 0 or n == p) else\n",
+ " 0 if (n < 0 or n < p) else\n",
+ " # Let's not blow the recursion stack: consider k copies of p in one recursion, not k\n",
+ " sum(prime_sum_ways(n - kp, i + 1) for kp in range(0, n + 1, p)))\n",
+ "\n",
+ "answer(euler_77, 71)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0074",
+ "metadata": {},
+ "source": [
+ "## [Problem 78](https://projecteuler.net/problem=78)\n",
+ "\n",
+ "This problem invlves counting partitions, just like [Problem 76](#Problem%2076), but my function `npartitions` is too slow to handle big numbers. I consulted the [Wikipedia article on the pentagonal number theorem](http://en.wikipedia.org/wiki/Pentagonal_number_theorem ) to get a formula that allows for a faster computation of partitions, and I made sure to apply the modulo operator to intermediate results; otherwise I would be creating big integers with hundreds of digits. I could have made `npartitions` a recursive function with a `@cache`, but explicitly managing the list `p` is faster."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 79,
+ "id": "a4d57e1f-3b9e-4d6f-9360-304f8fa0df2f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 78: Coin partitions 807 msec โ 55374 โ
"
+ ]
+ },
+ "execution_count": 79,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_78(M=million, limit=100_000):\n",
+ " \"\"\"Coin partitions: the least n for which p(n), the number of partitions of n,\n",
+ " is divisible by M. Uses the pentagonal number theorem:\n",
+ " p(n) = ฮฃ_{kโ 0} (-1)^(k-1) ยท p(n - g_k), g_k = k(3k-1)/2,\n",
+ " i.e. p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-12) + p(n-15) - ...\n",
+ " Searches up to limit; I had to guess at the limit.\"\"\"\n",
+ "\n",
+ " # The generalized pentagonal numbers g_k and their signs\n",
+ " alts = (j for i in range(1, limit) for j in (i, -i)) # 1, -1, 2, -2, ...\n",
+ " ks = list(takewhile(lambda k: pentagonal(k) <= limit, alts))\n",
+ " g = [pentagonal(k) for k in ks] # g[k] = k-th generalized pentagonal number\n",
+ " signs = [(-1) ** (k - 1) for k in ks] # signs[k] = its sign: +, +, -, -, +, +, ...\n",
+ " p = [1] # number of partitions of n, mod M\n",
+ " \n",
+ " def p_mod_M(n): \n",
+ " \"\"\"(number partitions of n) (mod M): signed sum over every pentagonal number g_k โค n.\"\"\"\n",
+ " p_n = sum(signs[k] * p[n - gk]\n",
+ " for k, gk in enumerate(takewhile(n.__ge__, g))) % M\n",
+ " p.append(p_n)\n",
+ " return p_n\n",
+ "\n",
+ " return first(n for n in range(1, limit) \n",
+ " if p_mod_M(n) == 0)\n",
+ "\n",
+ "answer(euler_78, 55374)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0075",
+ "metadata": {},
+ "source": [
+ "## [Problem 79](https://projecteuler.net/problem=79)\n",
+ "\n",
+ "I felt like this problem is not in general solvable; depending on the data there might be multiple answers. But under the assumption that there **is** a unique answer, my approach is to make a table of predecessors for each digit (within the successful logins), and then try every permutation of digits, and verify with regular expressions that each successful login matches the permutation. For the login `\"123\"` the regular exxpression is `\"1.*2.*3\"`. There are only 8! = 40,320 permutations to try, because the digts 4 and 5 are never used."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 80,
+ "id": "b18a810f-7cd6-4df8-94bc-c916c85ef340",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 79: Passcode Derivation 6 msec โ 73162890 โ
"
+ ]
+ },
+ "execution_count": 80,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_79(path=Path('0079_keylog.txt')):\n",
+ " \"\"\"Passcode Derivation: A security method is to ask the user for three random characters from a passcode, \n",
+ " e.g. 2nd, 3rd, and 5th. `keylog` contains fifty successful login attempts. Given that the three characters \n",
+ " are always asked for in order (e.g. never 5th, 3rd, 2nd), determine the shortest possible secret passcode.\"\"\"\n",
+ " keylog = read(path).split()\n",
+ " matchers = [re.compile('.*'.join(login)).search for login in keylog]\n",
+ " return first(int(n) for n in map(concat, permutations(set(concat(keylog))))\n",
+ " if all(match(n) for match in matchers))\n",
+ "\n",
+ "answer(euler_79, 73162890)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0076",
+ "metadata": {},
+ "source": [
+ "## [Problem 80](https://projecteuler.net/problem=80)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 81,
+ "id": "cell-0166",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 80: Square root digital expansion 1 msec โ 40886 โ
"
+ ]
+ },
+ "execution_count": 81,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_80(N=100):\n",
+ " \"\"\"Square root digital expansion: For the first one hundred natural numbers, find the total of the digital sums\n",
+ " of the first one hundred decimal digits for all the irrational square roots.\"\"\"\n",
+ " decimal.getcontext().prec = N+3\n",
+ " return sum(digital_sum(decimal.Decimal(i).sqrt(), N)\n",
+ " for i in ints(1, N) if not is_perfect_square(i))\n",
+ "\n",
+ "def digital_sum(x, N) -> int:\n",
+ " \"\"\"The sum of the first N digits in the number x (including digits on both side of decimal point, if any).\"\"\"\n",
+ " s = str(x).replace('.', '')[:N] # Get rid of the decimal point\n",
+ " return digitsum(int(s))\n",
+ "\n",
+ "def is_perfect_square(x: int) -> bool: \n",
+ " \"\"\"Is x a perfect square? It is if its square root is an integer.\"\"\"\n",
+ " return sqrt(x).is_integer()\n",
+ "\n",
+ "answer(euler_80, 40886)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f8536fec-470d-4e50-b39e-6ed7c748fc39",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 81โ90"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0077",
+ "metadata": {},
+ "source": [
+ "## [Problem 81](https://projecteuler.net/problem=81)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 82,
+ "id": "cell-0167",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 81: Path Sum, Two ways 3 msec โ 427337 โ
"
+ ]
+ },
+ "execution_count": 82,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_81(path=Path(\"p081_matrix.txt\")):\n",
+ " \"\"\"Path Sum, Two ways: Find the minimal path sum from the top left to the bottom right,\n",
+ " only moving right and down.\"\"\"\n",
+ " return minroute2ways(parse_matrix(read(path)))\n",
+ "\n",
+ "def minroute2ways(matrix) -> int:\n",
+ " \"\"\"Find the sum of the minimal route through matrix, moving 2 ways.\"\"\"\n",
+ " @cache\n",
+ " def cost(r, c) -> int:\n",
+ " \"\"\"Cost of path from (r, c) to (0, 0).\"\"\"\n",
+ " rest = (0 if r == 0 and c == 0 else\n",
+ " inf if r < 0 or c < 0 else\n",
+ " min(cost(r - 1, c), cost(r, c - 1)))\n",
+ " return matrix[r][c] + rest\n",
+ " # Start at end point, work backwards towards (0, 0)\n",
+ " return cost(len(matrix) - 1, len(matrix[0]) - 1) \n",
+ "\n",
+ "answer(euler_81, 427337)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0078",
+ "metadata": {},
+ "source": [
+ "## [Problem 82](https://projecteuler.net/problem=82)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 83,
+ "id": "eaff80dd-0329-4271-a54f-7245fa3276bc",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 82: Path Sum, Three Ways 388 msec โ 260324 โ
"
+ ]
+ },
+ "execution_count": 83,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_82(path=Path(\"0082_matrix.txt\")):\n",
+ " \"\"\"Path Sum, Three Ways: Now we can go up or down in a column; then move right.\"\"\"\n",
+ " return minroute3ways(parse_matrix(read(path)))\n",
+ "\n",
+ "def minroute3ways(matrix) -> int:\n",
+ " \"\"\"Find sum of minimum route through matrix, moving 3 ways (right and up/down).\n",
+ " You can start and end in any row in the first and last column,\"\"\"\n",
+ " width, height = len(matrix[0]), len(matrix)\n",
+ " @cache\n",
+ " def cost(r, c) -> int:\n",
+ " if c == 0:\n",
+ " return matrix[r][c]\n",
+ " else:\n",
+ " return min(cost(r1, c - 1) + column_cost(matrix, r1, r, c)\n",
+ " for r1 in range(height))\n",
+ " \n",
+ " # Find minimum cost to reach any row in the last column\n",
+ " return min(cost(r, width - 1) for r in range(height))\n",
+ "\n",
+ "def column_cost(matrix, r1, r2, c) -> int:\n",
+ " \"\"\"Calculate cost of moving from row r1 to row r2 in column c.\"\"\"\n",
+ " section = range(min(r1, r2), max(r1, r2) + 1)\n",
+ " return sum(matrix[r][c] for r in section)\n",
+ "\n",
+ "answer(euler_82, 260324)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "90084693-39a9-4280-b63a-ec15a5bdda47",
+ "metadata": {},
+ "source": [
+ "## [Problem 83](https://projecteuler.net/problem=83)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 84,
+ "id": "a5eb2986-c712-4a1c-8468-84459f89a737",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 83: Path Sum, Four Ways 6 msec โ 425185 โ
"
+ ]
+ },
+ "execution_count": 84,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_83(path=Path(\"0083_matrix.txt\")):\n",
+ " \"\"\"Path Sum, Four Ways: Now we can go in all four directions.\"\"\"\n",
+ " return minroute4ways(parse_matrix(read(path)))\n",
+ "\n",
+ "def minroute4ways(matrix) -> int:\n",
+ " \"\"\"Use Dijkstra's algorithm rather than @cache, to avoid infinite loops.\"\"\"\n",
+ " width, height = len(matrix[0]), len(matrix) # size of the matrix\n",
+ " costs = [[inf] * width for _ in range(height)] # minimal costs to get to (0, 0)\n",
+ " destination = (height - 1, width - 1) # start at (0, 0), go to destination\n",
+ " Q = [(matrix[0][0], 0, 0)] # priority queue of (partial_cost, row, col)\n",
+ " while Q:\n",
+ " (partialcost, r, c) = heapq.heappop(Q)\n",
+ " if (r, c) == destination:\n",
+ " return partialcost\n",
+ " # Continue if this path is better than previously-found paths.\n",
+ " if partialcost <= costs[r][c]:\n",
+ " for r2, c2 in moves4ways(r, c, width, height):\n",
+ " cost2 = partialcost + matrix[r2][c2]\n",
+ " if cost2 < costs[r2][c2]:\n",
+ " costs[r2][c2] = cost2\n",
+ " heapq.heappush(Q, (cost2, r2, c2))\n",
+ "\n",
+ "def moves4ways(r: int, c: int, width: int, height: int) -> list[tuple[int, int]]:\n",
+ " \"\"\"Move up, down, right, left.\"\"\"\n",
+ " return [(r + dr, c + dc) for (dr, dc) in ((-1, 0), (1, 0), (0, -1), (0, 1))\n",
+ " if 0 <= r + dr < height and 0 <= c + dc < width]\n",
+ "\n",
+ "answer(euler_83, 425185)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0079",
+ "metadata": {},
+ "source": [
+ "## [Problem 84](https://projecteuler.net/problem=84)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 85,
+ "id": "18003bf9-c1b2-4d74-a951-12c3238f65a1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 84: Monopoly odds 430 msec โ 101524 โ
"
+ ]
+ },
+ "execution_count": 85,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_84(sides=4, steps=million):\n",
+ " \"\"\"Monopoly odds: Find the most common 3 squares to land on in a Monopoly board,\n",
+ " assuming we are playing with dice with the given number of sides.\"\"\"\n",
+ " counts = Counter(monopoly(sides, steps))\n",
+ " return int(concat(f'{square:02d}' for (square, _) in counts.most_common(3)))\n",
+ "\n",
+ "Square = int # Data type for a square on the board: the square number\n",
+ "Deck = Iterator # Data type for a deck of cards; you can take the `next` one.\n",
+ "\n",
+ "def monopoly(sides: int, steps: int) -> Iterable[Square]:\n",
+ " \"\"\"Simulate a Monopoly game for `N` dice rolls, yielding the squares visited.\"\"\"\n",
+ " square = GO # Current location on board\n",
+ " doubles = 0 # Count of consecutive doubles rolled\n",
+ " for _ in range(steps):\n",
+ " yield square\n",
+ " d1, d2 = random.choices(range(1, sides + 1), k=2) # Roll two dice\n",
+ " square = (square + d1 + d2) % len(board) # Move ahead, maybe pass Go\n",
+ " doubles = (doubles + 1 if d1 == d2 else 0)\n",
+ " if square == G2J or doubles == 3: # Go to Jail\n",
+ " doubles = 0\n",
+ " square = JAIL \n",
+ " elif square in (CC1, CC2, CC3): # Community Chest card\n",
+ " square = do_card(next(CC_cards), square)\n",
+ " elif square in (CH1, CH2, CH3): # Chance card\n",
+ " square = do_card(next(CH_cards), square)\n",
+ "\n",
+ "(GO, A1, CC1, A2, T1, R1, B1, CH1, B2, B3,\n",
+ " JAIL, C1, U1, C2, C3, R2, D1, CC2, D2, D3, \n",
+ " FP, E1, CH2, E2, E3, R3, F1, F2, U2, F3, \n",
+ " G2J, G1, G2, CC3, G3, R4, CH3, H1, T2, H2) = board = range(40)\n",
+ "\n",
+ "RRs = {R1, R2, R3, R4}\n",
+ "\n",
+ "def deck(cards: list) -> Deck:\n",
+ " \"\"\"Make a deck of cards: an infinite iterable cycling through the (shuffled) cards.\"\"\"\n",
+ " random.shuffle(cards)\n",
+ " return cycle(cards)\n",
+ " \n",
+ "CC_cards = deck([None] * 14 + [GO, JAIL])\n",
+ "CH_cards = deck([None] * 6 + [GO, JAIL, C1, E3, H2, R1, RRs, RRs, {U1, U2}, -3])\n",
+ "\n",
+ "def do_card(card, square: Square) -> Square:\n",
+ " \"\"\"Update location from `square` to new location, depending on what `card` says.\"\"\"\n",
+ " return ( square if card is None # Don't move (card is about money)\n",
+ " else square - 3 if card == -3 # Go back 3 spaces\n",
+ " else card if isinstance(card, Square) # Go to square named on card\n",
+ " else min([s for s in card if s > square] or card)) # Advance to nearest\n",
+ "\n",
+ "answer(euler_84, 101524)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 86,
+ "id": "cell-0168",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 84: Monopoly odds 615 msec โ 101524 โ
"
+ ]
+ },
+ "execution_count": 86,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_84(sides=4, steps=million):\n",
+ " \"\"\"Monopoly odds: Find the most common 3 squares to land on in a Monopoly board,\n",
+ " assuming we are playing with dice with the given number of sides.\"\"\"\n",
+ " counts = monopoly(sides, steps)\n",
+ " return int(concat(f'{square:02d}' for (square, count) in counts.most_common(3)))\n",
+ "\n",
+ "# MONOPOLY\n",
+ "\n",
+ "(GO, A1, CC1, A2, T1, R1, B1, CH1, B2, B3,\n",
+ " JAIL, C1, U1, C2, C3, R2, D1, CC2, D2, D3, \n",
+ " FP, E1, CH2, E2, E3, R3, F1, F2, U2, F3, \n",
+ " G2J, G1, G2, CC3, G3, R4, CH3, H1, T2, H2) = board = range(40)\n",
+ "\n",
+ "Deck = deque\n",
+ "\n",
+ "CC_deck = Deck(shuffled([GO, JAIL] + 14 * [None]))\n",
+ "\n",
+ "CH_deck = Deck(shuffled([GO, JAIL, C1, E3, H2, R1, {R1, R2, R3, R4}, {R1, R2, R3, R4}, {U1, U2}, -3] + 6 * [None]))\n",
+ "\n",
+ "def monopoly(sides, steps) -> Counter:\n",
+ " \"\"\"Simulate given number of steps of Monopoly game, \n",
+ " yielding the number of the current square after each step.\"\"\"\n",
+ " goto(GO)\n",
+ " counts = Counter() #[0] * len(board)\n",
+ " doubles = 0\n",
+ " for _ in range(steps):\n",
+ " d1, d2 = random.randint(1, sides), random.randint(1, sides)\n",
+ " doubles = doubles + 1 if d1 == d2 else 0\n",
+ " goto(here + d1 + d2)\n",
+ " if here == G2J or doubles == 3:\n",
+ " doubles = 0\n",
+ " goto(JAIL)\n",
+ " elif here in (CC1, CC2, CC3):\n",
+ " do_card(CC_deck)\n",
+ " elif here in (CH1, CH2, CH3):\n",
+ " do_card(CH_deck)\n",
+ " if doubles == 0:\n",
+ " counts[here] += 1\n",
+ " return counts\n",
+ "\n",
+ "def goto(square) -> None:\n",
+ " global here\n",
+ " here = square % len(board) \n",
+ "\n",
+ "def do_card(deck) -> None:\n",
+ " \"\"\"Take the top card from deck and do what it says.\"\"\"\n",
+ " card = deck[0] # The top card\n",
+ " deck.rotate(-1) # Move top card to bottom of deck\n",
+ " if card == None: # Don't move\n",
+ " pass\n",
+ " elif card == -3: # Go back 3 spaces\n",
+ " goto(here - 3)\n",
+ " elif isinstance(card, set): # Advance to next railroad or utility\n",
+ " goto(min({place for place in card if place > here} or card))\n",
+ " else: # Go to destination named on card\n",
+ " goto(card)\n",
+ "\n",
+ "answer(euler_84, 101524)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0080",
+ "metadata": {},
+ "source": [
+ "## [Problem 85](https://projecteuler.net/problem=85)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 87,
+ "id": "cell-0169",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 85: Counting rectangles 807 msec โ 2772 โ
"
+ ]
+ },
+ "execution_count": 87,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_85(N=2*million):\n",
+ " \"\"\"Counting rectangles: find the area of the matrix that contains a number of sub-rectangles closest to 2 million.\"\"\"\n",
+ " # By experimentation, nrectangles(100, 100) = 25,502,500, so\n",
+ " # I'll limit one dimension to cube root of N, and increment the other dimension until nrectangles > N+delta\n",
+ " # So I guess that \n",
+ " best = (N-1, 1, 1) # delta, i, j\n",
+ " for i in ints(1, int(N ** (1/3))):\n",
+ " for j in integers(start=1):\n",
+ " n = nrectangles(i, j)\n",
+ " delta = abs(n - N)\n",
+ " if n > N + best[0]:\n",
+ " # Making j larger would just make nrectangles too big.\n",
+ " break\n",
+ " best = min(best, (delta, i, j))\n",
+ " return best[1] * best[2]\n",
+ "\n",
+ "def nrectangles(i, j) -> int:\n",
+ " \"\"\"To find the number of rectangles in a ixj matrix, count all the\n",
+ " rectangles of size axb for axb from 1x1 up to ixj. To count how\n",
+ " many ways each fits, count the upper left corners they can go in.\"\"\"\n",
+ " def nrectangles_of_size(a, b): \n",
+ " \"\"\"How many rectangles of exactly size axb fit into the big rectangle of size ixj?\"\"\"\n",
+ " return (i + 1 - a) * (j + 1 - b)\n",
+ " return sum(nrectangles_of_size(a, b) for a in ints(1, i) for b in ints(1, j))\n",
+ "\n",
+ "answer(euler_85, 2772)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fff8a173-a680-4c27-a7ec-1b11df430561",
+ "metadata": {},
+ "source": [
+ "## [Problem 86](https://projecteuler.net/problem=86)\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 88,
+ "id": "547f3d89-a358-4c88-a0a8-703c788c4de5",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 86: Cuboid Route 242 msec โ 1818 โ
"
+ ]
+ },
+ "execution_count": 88,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_86(limit=1_000_000):\n",
+ " \"\"\"Cuboid Route: Find the least value of M such that the number of solutions to the problem of\n",
+ " finding integral shortest paths between opposite corners on a cuboid first exceeds one million.\"\"\"\n",
+ " solutions = 0\n",
+ " for M in integers(start=1):\n",
+ " solutions += cuboids_with_long_side(M)\n",
+ " if solutions > limit:\n",
+ " return M\n",
+ "\n",
+ "def cuboids_with_long_side(M: int) -> int:\n",
+ " \"\"\"Count of integral shortest paths on a cuboid with longest side exactly M.\n",
+ " The two other sides sum to S.\n",
+ " If the diagonal (M ** 2 + S ** 2) is a perfect square, count the solutions.\"\"\"\n",
+ " # Usually S can be split into K + L in S // 2 non-redundant ways\n",
+ " # E.g. S = 6 implies ways = 3: {1+5, 2+4, 3+3}\n",
+ " # But if S > M, only count ways in which both K and L are <= M\n",
+ " return sum(S // 2 if S <= M else (S // 2) - (S - M) + 1\n",
+ " for S in range(2, 2 * M)\n",
+ " if is_perfect_square(M ** 2 + S ** 2))\n",
+ "\n",
+ "answer(euler_86, 1818)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0081",
+ "metadata": {},
+ "source": [
+ "## [Problem 87](https://projecteuler.net/problem=87)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 89,
+ "id": "c2391f60-280d-48dc-b0c8-a8fa8123d572",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 87: Prime power triples 559 msec โ 1097343 โ
"
+ ]
+ },
+ "execution_count": 89,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_87(limit=50*million):\n",
+ " \"\"\"Prime power triples: How many numbers below fifty million can be expressed as the sum of\n",
+ " a prime square, prime cube, and prime fourth power?\"\"\"\n",
+ " # I can enumerate all possibilities, but I need to form a set, not a list, of numbers, since \n",
+ " # some numbers can be made multiple ways, e.g. 2**2 + 5**3 + 2**4 == 11**2 + 2**3 + 2**4\n",
+ " return len({p ** 2 + q ** 3 + r ** 4\n",
+ " for p in primerange(limit ** (1/2))\n",
+ " for q in primerange((limit - p ** 2) ** (1/3))\n",
+ " for r in primerange((limit - p ** 2 - q ** 3) ** (1/4))})\n",
+ "\n",
+ "answer(euler_87, 1097343)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "129e8943-e20f-4b1a-9224-77e9d38ca46b",
+ "metadata": {},
+ "source": [
+ "## [Problem 88](https://projecteuler.net/problem=88)\n",
+ "\n",
+ "Analysis: Consider the *k* element set *s* = {*k*, 2, 1, 1, ...} (with *k* - 2 copies of 1). Here ฮฃ*s* = โ*s* = 2*k*. Thus, *2k* is always a product-sum number for *k*. Also, if we have a set *s* whose product is *n* and whose sum is less than *n*, then we can make *n* be a product-sum number by adding 1's to the set. So we can start with that as an answer for each *k*, then recursively add factors using `find_factors` and update the table `min_product_sum[k]` whenever we find a better answer."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 90,
+ "id": "6eeb24d7-604a-4946-ba1a-ff64c1980e57",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 88: Product-sum Numbers 76 msec โ 7587457 โ
"
+ ]
+ },
+ "execution_count": 90,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_88(k_limit=12_000):\n",
+ " \"\"\"Product-sum Numbers: What is the sum of all the minimal product-sum numbers for 2 <= k <= 12,000?\n",
+ " For example, 6 is a product-sum number because there is a set s = {1, 2, 3} such that ฮฃs = โs = 6.\n",
+ " A minimal product-sum number is the smallest product-sum number for a set of size k.\"\"\"\n",
+ " n_max = 2 * k_limit\n",
+ " min_product_sum = [n_max for k in range(k_limit + 1)]\n",
+ "\n",
+ " def find_factors(partial_prod, partial_sum, factor_count, start_factor) -> None:\n",
+ " \"\"\"Recursively enumerate non-decreasing multisets of factors (each >= 2) and record,\n",
+ " for each, the `min_product_sum` number it produces at the appropriate set size k.\"\"\"\n",
+ " # What would k be if we filled things out with 1s?\n",
+ " k = partial_prod - partial_sum + factor_count\n",
+ " if k <= k_limit:\n",
+ " min_product_sum[k] = min(min_product_sum[k], partial_prod)\n",
+ " for factor in range(start_factor, (n_max // partial_prod) * 2 + 1):\n",
+ " find_factors(partial_prod * factor, partial_sum + factor, factor_count + 1, factor)\n",
+ "\n",
+ " # Start the recursion with an initial factor of 2\n",
+ " for first_factor in range(2, isqrt(n_max) + 1):\n",
+ " find_factors(first_factor, first_factor, 1, first_factor)\n",
+ " return sum(set(min_product_sum[2:k_limit+1]))\n",
+ "\n",
+ "answer(euler_88, 7587457)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0083",
+ "metadata": {},
+ "source": [
+ "## [Problem 89](https://projecteuler.net/problem=89)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 91,
+ "id": "ab9986fe-616d-4828-9821-0a9796c0c74f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 89: Roman numerals 4 msec โ 743 โ
"
+ ]
+ },
+ "execution_count": 91,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_89(path=Path('p089_roman.txt')):\n",
+ " \"\"\"Roman numerals: Find the number of characters saved by writing each of the roman numerals\n",
+ " in roman.txt in their minimal form (e.g. as IV rather than IIII).\"\"\"\n",
+ " original = read(path).split()\n",
+ " efficient = [roman_from_int(int_from_roman(r)) for r in original]\n",
+ " return len(concat(original)) - len(concat(efficient))\n",
+ "\n",
+ "roman = dict(M=1000, CM=900, D=500, CD=400, C=100, XC=90, L=50, XL=40, X=10, IX=9, V=5, IV=4, I=1)\n",
+ "roman_regex = 'CM|CD|XC|XL|IX|IV|M|D|C|L|X|V|I' # Longer ones first\n",
+ "\n",
+ "def int_from_roman(roman_str: str) -> int:\n",
+ " \"\"\"Convert this string of roman numerals to an int.\"\"\"\n",
+ " return sum(roman[r] for r in re.findall(roman_regex, roman_str))\n",
+ "\n",
+ "def roman_from_int(n: int) -> str:\n",
+ " \"\"\"Convert this integer to a minimal-length roman numeral string.\"\"\"\n",
+ " return '' if n == 0 else next(r + roman_from_int(n - i) \n",
+ " for r, i in roman.items() if i <= n)\n",
+ "\n",
+ "answer(euler_89, 743)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0084",
+ "metadata": {},
+ "source": [
+ "## [Problem 90](https://projecteuler.net/problem=90)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 92,
+ "id": "cell-0172",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 90: Cube digit pairs 9 msec โ 1217 โ
"
+ ]
+ },
+ "execution_count": 92,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_90(faces='0123456786', N=6, targets='01 04 06 16 25 36 46 64 81'.split()):\n",
+ " \"\"\"Cube digit pairs: How many distinct arrangements of the two cubes allow for all of the target numbers\n",
+ " to be displayed? By default, N=6-sided dice; faces can be any digit but 6 and 9 can be turned upside down;\n",
+ " targets are the square numbers from 01 to 81.\"\"\"\n",
+ " dice = list(combinations(faces, N))\n",
+ " # t,d are target digits; A,B are dice (each with N faces).\n",
+ " # Count how many distinct A,B pairs can make all the targets (one target digit per die) \n",
+ " return quantify(all((t in A and d in B) or (t in B and d in A)\n",
+ " for (t, d) in targets)\n",
+ " for A, B in combinations(dice, 2))\n",
+ "\n",
+ "answer(euler_90, 1217)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "053b2b5b-5aca-4f2c-857c-768c5a9efb74",
+ "metadata": {},
+ "source": [
+ "# Project Euler Problems 91โ100"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4d540954-8f8b-49a7-a6aa-bbc5e8cef619",
+ "metadata": {},
+ "source": [
+ "## [Problem 91](https://projecteuler.net/problem=91)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 93,
+ "id": "614d95dc-eefa-4001-b9ac-185e2dff2390",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 91: Right Triangles with Integer Coordinates 769 msec โ 14234 โ
"
+ ]
+ },
+ "execution_count": 93,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_91(limit=51):\n",
+ " \"\"\"Right Triangles with Integer Coordinates: how many right triangles can be formed\n",
+ " on a square matrix where each vertex has x and y coordinates in range(0, limit)?\"\"\"\n",
+ " return sum(y2 * x1 < y1 * x2 and is_right_triangle(x1, y1, x2, y2)\n",
+ " for x1 in range(limit)\n",
+ " for y1 in range(limit)\n",
+ " for x2 in range(limit)\n",
+ " for y2 in range(limit))\n",
+ "\n",
+ "def is_right_triangle(x1, y1, x2, y2) -> bool:\n",
+ " \"\"\"Do the vertexes {(0,0), (x1,y1), (x2,y2)} form a right triangle?\"\"\" \n",
+ " a2 = x1**2 + y1**2\n",
+ " b2 = x2**2 + y2**2\n",
+ " c2 = (x2 - x1)**2 + (y2 - y1)**2\n",
+ " return (a2 + b2 == c2) or (b2 + c2 == a2) or (c2 + a2 == b2)\n",
+ "\n",
+ "\n",
+ "answer(euler_91, 14234)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0085",
+ "metadata": {},
+ "source": [
+ "## [Problem 92](https://projecteuler.net/problem=92)\n",
+ "\n",
+ "This *was* my slowest-running problem, taking about 15 seconds, straightforwardly applying `square_digit_chain` (with a `@cache`) to every integer from 1 to 10 million. \n",
+ "- I had an idea! Addition is commutative, so the sum of the squares for 3456789 is the same as for 6345789, and for all 7! = 720 permutations of the digits. So I canonicalized all the integers from 1 to 10 million (arbitrarily choosing the smallest of the permutations as the canonical one). The good news was that this got us down from 10 million to just 11,439 distinct integers, which could then be handled by `square_digit_chain` in about 20 milliseconds. The bad news was that it still took 4 seconds to do the canonicalization.\n",
+ "- I had another idea! Directly generate the combinations of digits. That's easy enough with `itertools.combinations_with_replacement`, but I need to know how many actual integers each canonical form represents. For example, 3456789 represents 7! distinct integers, 1111111 represents only one, and 1111123 represents 42 (the 2 can be in any of 7 places and the 3 in any of the remaining 6 places). With this optimization, the time is down to about 50 milliseconds."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 94,
+ "id": "0ce6dd51-9058-4180-9c72-bd87d8df8a53",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 92: Square digit chains 34 msec โ 8581146 โ
"
+ ]
+ },
+ "execution_count": 94,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_92(max_digits=7):\n",
+ " \"\"\"Square digit chains: A number chain is created by continuously adding the square of the digits in a number \n",
+ " to form a new number until it has been seen before. EVERY starting number will eventually arrive at 1 or 89.\n",
+ " How many starting numbers below ten million will arrive at 89?\"\"\"\n",
+ " counts = canonical_number_counts(max_digits)\n",
+ " return sum(counts[c] for c in counts if square_digit_chain(c) == 89)\n",
+ "\n",
+ "def square_digit_chain(n: int) -> int:\n",
+ " \"\"\"What does this chain of squared-digit-sums eventually end up at, 1 or 89?\"\"\"\n",
+ " while n not in (1, 89):\n",
+ " n = sum(map(square, digitlist(n)))\n",
+ " return n \n",
+ "\n",
+ "def canonical_number_counts(Ndigits=7) -> dict[int: int]:\n",
+ " \"\"\"A dict of {canonical_number: how many integers in [1, 10**digits) it is a permutation of}.\n",
+ " A canonical number is the minimum of all the permutations.\n",
+ " The count is the multinomial coefficient: Ndigits! / โ digit_count!\n",
+ " For example, {3456789: 720, 1111111: 1, 1111123: 42}.\"\"\"\n",
+ " digit_combos = combinations_with_replacement(\"0123456789\", Ndigits)\n",
+ " return {int(concat(digits)): factorial(Ndigits) // prod(map(factorial, Counter(digits).values()))\n",
+ " for digits in islice(digit_combos, 1, None)} # islice to skip (\"0\", ...)\n",
+ "\n",
+ "answer(euler_92, 8581146)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0086",
+ "metadata": {},
+ "source": [
+ "## [Problem 93](https://projecteuler.net/problem=93)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 95,
+ "id": "cell-0174",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 93: Arithmetic expressions 39 msec โ 1258 โ
"
+ ]
+ },
+ "execution_count": 95,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_93():\n",
+ " \"\"\"Arithmetic expressions: By using each of {1, 2, 3, 4}, exactly once, and the operations (+, -, *, /),\n",
+ " it is possible to form different positive integer targets. Find the set of four distinct digits,\n",
+ " a < b < c < d, for which the longest set of consecutive positive integers, 1 to n, can be obtained,\n",
+ " giving your answer as a string: abcd.\"\"\"\n",
+ " return int(concat(max(combinations(ints(1, 9), 4), key=expressiveness)))\n",
+ "\n",
+ "def expressiveness(Nums):\n",
+ " \"\"\"How many consecutive integers, 1 to n, can be made from the set Nums?\"\"\"\n",
+ " values = expression_values(frozenset(Nums))\n",
+ " for i in integers(start=1):\n",
+ " if i not in values:\n",
+ " return i-1\n",
+ "\n",
+ "@cache\n",
+ "def expression_values(Nums):\n",
+ " \"\"\"Given a set of numbers, return a set of expression values (numbers) that \n",
+ " can be made from arithmetic expressions (+, -, *, /) involving the numbers.\"\"\"\n",
+ " if len(Nums) == 1:\n",
+ " return Nums\n",
+ " else:\n",
+ " # Split Nums into non-empty sets A and B; recursively find all expressions\n",
+ " # values that can be made with A and B, and combine them in all possible ways.\n",
+ " result = set()\n",
+ " for A in map(frozenset, powerset(Nums)):\n",
+ " B = Nums - A\n",
+ " if A and B:\n",
+ " for a in expression_values(A):\n",
+ " for b in expression_values(B):\n",
+ " result.update({a + b, a * b, a - b, b - a})\n",
+ " if b != 0: result.add(a / b)\n",
+ " if a != 0: result.add(b / a)\n",
+ " return result\n",
+ "\n",
+ "answer(euler_93, 1258)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8d7dc721-5343-4356-a6f1-be0c5ef8350a",
+ "metadata": {},
+ "source": [
+ "## [Problem 95](https://projecteuler.net/problem=95)\n",
+ "\n",
+ "We saw Amicable numbers before, in Problem 21, and I tried the same approach here, but it was taking about 7 seconds just to do `divisors(n, proper=True)` for each `n` up to a million. So instead I created a list `d` of proper divisor sums, using a sieve technique (see `proper_divisor_sums`). There is some ambiguity anout the word **chain**, so instead I'll use the words **path** and **cycle**, Consider:\n",
+ "\n",
+ "18922 โ 9464 โ **12496** โ 14288 โ 15472 โ 14536 โ 14264 โ **12496**\n",
+ "\n",
+ "Is this a chain? I will call it a **path** that has a two-element lead-in, 18922 โ 9464, followed by a five-element **cycle**. My function `amicable_cycle`, when given `n=12496` as the starting number, will return `[12496, 14288, 15472, 14536, 14264]`, dropping the lead-in elements. That's what the problem intended.\n",
+ "\n",
+ "*Note*: this was taking over a second to run, so I changed the `seen` variable from a Python `set` to a `bytearray`, and used `numpy` vectorization inside of `proper_divisor_sums`. That got it down to about 0.6 seconds."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 96,
+ "id": "73a5d5f2-620b-4010-9fc3-6737ac45ceed",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 95: Amicable chains 569 msec โ 14316 โ
"
+ ]
+ },
+ "execution_count": 96,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_95(limit=million+1):\n",
+ " \"\"\"Amicable chains: Find the smallest member of the longest amicable cycle\n",
+ " whose every element does not exceed one million.\"\"\"\n",
+ " d = proper_divisor_sums(limit) # d[n] = sum of proper divisors of n,\n",
+ " seen = bytearray(limit) # numbers already seen in a path\n",
+ " best = set() # best (longest) cycle found so far\n",
+ " for n in range(2, limit):\n",
+ " cycle = amicable_cycle(n, d, limit, seen)\n",
+ " best = max(best, cycle, key=len)\n",
+ " return min(best)\n",
+ "\n",
+ "def amicable_cycle(n, d, limit, seen) -> list[int]:\n",
+ " \"\"\"Form an amicable path starting with n, and look for a cycle.\n",
+ " If any number in the chain exceeds limit or is in `seen`, stop.\n",
+ " A chain might be a \"tail\" leading into a \"cycle\"; return only the cycle part.\n",
+ " Mutate `seen` by adding in all the numbers we have seen here;\n",
+ " the next call to amicable_cycle will have `seen` them.\"\"\"\n",
+ " path = [] # The list of numbers in the chain\n",
+ " index = {} # index[n] is the index into path where n occurs\n",
+ " while n <= limit and not seen[n]:\n",
+ " index[n] = len(path)\n",
+ " path.append(n)\n",
+ " seen[n] = True\n",
+ " n = d[n]\n",
+ " # If n is in index then we found a cycle; otherwise n exceeded `limit` or was previously `seen` \n",
+ " return path[index[n]:] if (n in index) else []\n",
+ "\n",
+ "def proper_divisor_sums(limit) -> np.array:\n",
+ " \"\"\"Make an np.array d, where d[n] is the sum of the proper divisors of n.\"\"\"\n",
+ " # Use a sieve, adding each divisor to the sum for each multiple of the divisor\n",
+ " d = np.zeros(limit, dtype=np.int32)\n",
+ " for divisor in range(1, limit // 2 + 1):\n",
+ " d[2*divisor:limit:divisor] += divisor # vectorized assignment\n",
+ " return d\n",
+ "\n",
+ "answer(euler_95, 14316)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0087",
+ "metadata": {},
+ "source": [
+ "## [Problem 96](https://projecteuler.net/problem=96)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "id": "cell-0175",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 96: SuDoku 51 msec โ 24702 โ
"
+ ]
+ },
+ "execution_count": 97,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_96(path=Path(\"p096_sudoku.txt\")):\n",
+ " \"\"\"SuDoku: Solve puzzles and return the sum of `scores`.\"\"\"\n",
+ " pictures = re.split(r\"Grid.*\", read(path))\n",
+ " return sum(sudoku_score(sudoku_search(constrain(parse_sudoku_matrix(picture))))\n",
+ " for picture in pictures if len(picture) >= 81)\n",
+ "\n",
+ "# SUDOKU \n",
+ "\n",
+ "DigitSet = str # e.g. '123'\n",
+ "Square = str # e.g. 'A9'\n",
+ "Picture = str # e.g. \"53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79\"\n",
+ "matrix = dict # E.g. {'A9': '123', ...}, a dict of {Square: DigitSet}\n",
+ "Fail = matrix() # The empty matrix is used to indicate failure to find a solution\n",
+ "\n",
+ "def cross(A, B) -> tuple:\n",
+ " \"\"\"Cross product of strings in A and strings in B.\"\"\"\n",
+ " return tuple(a + b for a in A for b in B)\n",
+ "\n",
+ "rows = 'ABCDEFGHI'\n",
+ "cols = one_nine\n",
+ "squares = cross(rows, cols)\n",
+ "all_boxes = [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')]\n",
+ "all_units = [cross(rows, c) for c in cols] + [cross(r, cols) for r in rows] + all_boxes\n",
+ "units = {s: tuple(u for u in all_units if s in u) for s in squares}\n",
+ "peers = {s: set().union(*units[s]) - {s} for s in squares}\n",
+ "\n",
+ "def sudoku_score(matrix: Matrix) -> int:\n",
+ " \"\"\"The score is the concatenation of the three digits in the upper left top row.\"\"\"\n",
+ " if not matrix:\n",
+ " print('failed:', matrix)\n",
+ " return 0\n",
+ " return int(matrix['A1'] + matrix['A2'] + matrix['A3'])\n",
+ "\n",
+ "def parse_sudoku_matrix(picture: str) -> matrix:\n",
+ " \"\"\"Convert a Picture to a matrix.\"\"\"\n",
+ " vals = re.findall(r\"[0-9]\", picture)\n",
+ " assert len(vals) == 81\n",
+ " return {s: one_nine if (v == '0') else v\n",
+ " for s, v in zip(squares, vals)}\n",
+ "\n",
+ "def fill(matrix, s, d) -> Matrix:\n",
+ " \"\"\"Eliminate all the other digits (except d) from matrix[s].\"\"\"\n",
+ " if matrix[s] == d or all(eliminate(matrix, s, d2) for d2 in matrix[s] if d2 != d):\n",
+ " return matrix\n",
+ " else:\n",
+ " return Fail\n",
+ "\n",
+ "def eliminate(matrix, s, d) -> Matrix:\n",
+ " \"\"\"Eliminate d from matrix[s]; implement the two constraint propagation strategies.\"\"\"\n",
+ " if d not in matrix[s]:\n",
+ " return matrix ## Already eliminated\n",
+ " matrix[s] = matrix[s].replace(d, '')\n",
+ " if not matrix[s]:\n",
+ " return Fail ## Fail: no legal digit left\n",
+ " elif len(matrix[s]) == 1:\n",
+ " # 1. If a square has only one possible digit, then eliminate that digit from the square's peers.\n",
+ " d2 = matrix[s]\n",
+ " if not all(eliminate(matrix, s2, d2) for s2 in peers[s]):\n",
+ " return Fail ## Fail: can't eliminate d2 from some square\n",
+ " for u in units[s]:\n",
+ " dplaces = [s for s in u if d in matrix[s]]\n",
+ " # 2. If a unit has only one possible square that can hold a digit, then fill the square with the digit.\n",
+ " if not dplaces or (len(dplaces) == 1 and not fill(matrix, dplaces[0], d)):\n",
+ " return Fail ## Fail: no place in u for d\n",
+ " return matrix\n",
+ "\n",
+ "def constrain(matrix) -> Matrix:\n",
+ " \"\"\"Propagate constraints on a copy of matrix to yield a new constrained matrix.\"\"\"\n",
+ " constrained: matrix = {s: one_nine for s in squares}\n",
+ " for s in matrix:\n",
+ " d = matrix[s]\n",
+ " if len(d) == 1:\n",
+ " fill(constrained, s, d)\n",
+ " return constrained\n",
+ "\n",
+ "def sudoku_search(matrix) -> Matrix:\n",
+ " \"\"\"Depth-first search with constraint propagation (`fill`) to find a solution.\"\"\"\n",
+ " if matrix is Fail: \n",
+ " return Fail\n",
+ " unfilled = [s for s in squares if len(matrix[s]) > 1]\n",
+ " if not unfilled: \n",
+ " return matrix\n",
+ " s = min(unfilled, key=lambda s: len(matrix[s]))\n",
+ " for d in matrix[s]:\n",
+ " if (solution := sudoku_search(fill(matrix.copy(), s, d))):\n",
+ " return solution\n",
+ " return Fail\n",
+ "\n",
+ "answer(euler_96, 24702)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0088",
+ "metadata": {},
+ "source": [
+ "## [Problem 97](https://projecteuler.net/problem=97)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 98,
+ "id": "cell-0177",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 97: Large non-Mersenne prime 0 msec โ 8739992577 โ
"
+ ]
+ },
+ "execution_count": 98,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_97(M=10000*million):\n",
+ " \"\"\"Large non-Mersenne prime: ???\"\"\"\n",
+ " return (28433 * pow(2, 7830457, M) + 1) % M\n",
+ "\n",
+ "answer(euler_97, 8739992577)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0089",
+ "metadata": {},
+ "source": [
+ "## [Problem 98](https://projecteuler.net/problem=98)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 99,
+ "id": "cell-0178",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 98: Anagramic squares 40 msec โ 18769 โ
"
+ ]
+ },
+ "execution_count": 99,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_98(path=Path('p098_words.txt')):\n",
+ " \"\"\"Anagramic squares: What is the largest square number formed by any member of such a pair?.\"\"\"\n",
+ " words = literal_eval(read(path))\n",
+ " pairs = anagrams(words)\n",
+ " L = max(len(w1) for (w1, w2) in pairs)\n",
+ " squares = set(str(i**2) for i in range(int(10**(L/2.))))\n",
+ " squaresby = dict(groupby(squares, signature))\n",
+ " return max(result[0]\n",
+ " for (w1, w2) in anagrams(words)\n",
+ " for result in squarepairs(w1, w2, squares, squaresby))\n",
+ "\n",
+ "def anagrams(words) -> Iterable[tuple[str, str]]:\n",
+ " \"\"\"Generate all pairs of words that are anagrams of each other.\"\"\"\n",
+ " for group in groupby(words, key=lambda n: sorted_characters(n)).values():\n",
+ " yield from combinations(group, 2)\n",
+ "\n",
+ "def signature(word) -> str:\n",
+ " \"\"\" 'BOOKKEEPER' => '0112233435'; 'CARE', '1296' => '0123' \"\"\"\n",
+ " T = {}\n",
+ " for L in word:\n",
+ " if L not in T:\n",
+ " n = len(T)\n",
+ " if n >= 10: return ''\n",
+ " T[L] = '0123456789'[n]\n",
+ " return concat(T[L] for L in word)\n",
+ "\n",
+ "def squarepairs(w1, w2, squares, squaresby) -> Iterable[tuple[int, int, int, int]]:\n",
+ " \"\"\"squarepairs('CARE', 'RACE', ...) => (1296, 9216, 'CARE', 'RACE')\"\"\"\n",
+ " for s1 in squaresby[signature(w1)]:\n",
+ " s2 = w2.translate(str.maketrans(w1, s1))\n",
+ " if s2 in squares:\n",
+ " i1, i2 = sorted([int(s1), int(s2)])\n",
+ " yield (i2, i1, w1, w2)\n",
+ "\n",
+ "answer(euler_98, 18769)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hdr-0090",
+ "metadata": {},
+ "source": [
+ "## [Problem 99](https://projecteuler.net/problem=99)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 100,
+ "id": "cell-0179",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " 99: Largest exponential 3 msec โ 709 โ
"
+ ]
+ },
+ "execution_count": 100,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def euler_99(path=Path('0099_base_exp.txt')):\n",
+ " \"\"\"Largest exponential: Which line number in `data` has the b,e pair that maximizes b ** e?\"\"\"\n",
+ " pairs = map(literal_eval, read(path).splitlines())\n",
+ " # Use logarithms to prevent very big integers. \n",
+ " _, line_number = max((e * log(b), line_number) \n",
+ " for (line_number, (b, e)) in enumerate(pairs, 1))\n",
+ " return line_number\n",
+ "\n",
+ "answer(euler_99, 709)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bb5e0dee-94f7-4f35-8c95-a7328b340486",
+ "metadata": {
+ "deletable": true,
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "# Obsolete Primes class\n",
+ "\n",
+ "The `sympy` module has a great implementation of various functions related to primes, which I have used extensivly. Before `sympy` was released in 2007 I had my own `Primes` class, which implemented the [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) algorithm. I include the class here, even though I no longer use it. You can also see [my other notebook about primes](TruncatablePrimes.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 101,
+ "id": "b8a6b6dd-198a-4fac-972d-b7ef1db68355",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Primes:\n",
+ " \"\"\"This class can be used as follows:\n",
+ " โ = Primes() # an ieterable with the following methods:\n",
+ " for p in โ: # iterate over infinite sequence of primes\n",
+ " 37 in โ => True # primality test\n",
+ " โ[1] => 3 # nth prime\n",
+ " โ[5:9] => [13, 17, 19, 23] # slice of primes\n",
+ " โ.upto(12) => 2 3 5 7 11 # iterate through primes up to a limit\"\"\"\n",
+ "\n",
+ " def __init__(self, n=million):\n",
+ " \"\"\"Create an iterable generator of primes, with initial cache of all primes <= n\n",
+ " .\"\"\"\n",
+ " # sieve keeps track of odd numbers: sieve[i] is True iff (2*i + 1) has no factors (yet) \n",
+ " N = n // 2 # length of sieve\n",
+ " sieve = [True] * N\n",
+ " for i in range(3, isqrt(n) + 1, 2):\n",
+ " if sieve[i // 2]: # i is prime\n",
+ " # Mark start, start + i, start + 2i, ... as non-prime\n",
+ " start = i ** 2 // 2\n",
+ " sieve[start::i] = repeat(False, len(range(start, N, i)))\n",
+ " self._list = [2] + [2*i+1 for i in range(1, N) if sieve[i]]\n",
+ " self._set = set(self._list)\n",
+ " self.maxn = n # We have tested for all primes < self.maxn\n",
+ "\n",
+ " def __contains__(self, n) -> bool:\n",
+ " \"\"\"Is n a prime?\"\"\"\n",
+ " # If n is small, look in _set; otherwise use Fermat's Little Theorem\n",
+ " return n in self._set if n <= self.maxn else is_probably_prime(n)\n",
+ "\n",
+ " def __getitem__(self, index) -> int:\n",
+ " \"Return the ith prime, or a slice: primes[0] = 2; primes[1] = 3; primes[1:4] = [3, 5, 7].\"\n",
+ " stop = (index.stop if isinstance(index, slice) else index)\n",
+ " while len(self._list) <= stop:\n",
+ " # If asked for the ith prime and we don't have it yet, we will expand the cache.\n",
+ " self.__init__(2 * self.maxn)\n",
+ " return self._list[index]\n",
+ "\n",
+ " def upto(self, n) -> Iterable[int]:\n",
+ " \"\"\"Yield all primes <= n.\"\"\"\n",
+ " if self.maxn < n:\n",
+ " self.__init__(max(n + 1, 2 * self.maxn))\n",
+ " for p in self._list:\n",
+ " if p > n:\n",
+ " return\n",
+ " yield p\n",
+ "\n",
+ "def is_probably_prime(n: int, k=20) -> bool:\n",
+ " \"\"\"If is_probably_prime(n) returns False then n is definitely composite; if True then n is probably prime.\n",
+ " Increase `k` to decrease the probability of false positives.\"\"\"\n",
+ " if n <= 10:\n",
+ " return n in (2, 3, 5, 7)\n",
+ " else:\n",
+ " samples = (random.randrange(2, n) for _ in range(k))\n",
+ " return all(pow(a, n - 1, n) == 1 for a in samples) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8aead06a-0e3f-4122-8e43-8128a24a3c25",
+ "metadata": {},
+ "source": [
+ "# Summary of Answers\n",
+ "\n",
+ "Here are all my answers and their run times. I didn't do 5 problems, but the other 95 all run in under a second each."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 102,
+ "id": "3ce96f67-2432-49e2-bf49-e7bc690c217a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Missing problems: [64, 65, 66, 94, 100]\n",
+ "Run time in seconds: total: 12.7, max: 0.9, mean: 0.134, median: 0.012\n",
+ "\n",
+ " 1: Multiples of 3 and 5 0 msec โ 233168 โ
\n",
+ " 2: Even Fibonacci numbers 0 msec โ 4613732 โ
\n",
+ " 3: Largest prime factor 0 msec โ 6857 โ
\n",
+ " 4: Largest palindrome product 46 msec โ 906609 โ
\n",
+ " 5: Smallest multiple 0 msec โ 232792560 โ
\n",
+ " 6: Sum square difference 0 msec โ 25164150 โ
\n",
+ " 7: 10,001st prime 3 msec โ 104743 โ
\n",
+ " 8: Largest product in a series 0 msec โ 23514624000 โ
\n",
+ " 9: Special Pythagorean triplet 13 msec โ 31875000 โ
\n",
+ " 10: Summation of primes 50 msec โ 142913828922 โ
\n",
+ " 11: Largest product in a matrix 1 msec โ 70600674 โ
\n",
+ " 12: Highly divisible triangular number 155 msec โ 76576500 โ
\n",
+ " 13: Large sum 0 msec โ 5537376230 โ
\n",
+ " 14: Longest Collatz sequence 465 msec โ 837799 โ
\n",
+ " 15: Lattice paths 0 msec โ 137846528820 โ
\n",
+ " 16: Power digit sum 0 msec โ 1366 โ
\n",
+ " 17: Number letter counts 0 msec โ 21124 โ
\n",
+ " 18: Maximum path sum I 0 msec โ 1074 โ
\n",
+ " 19: Counting Sundays 6 msec โ 171 โ
\n",
+ " 20: Factorial digit sum 0 msec โ 648 โ
\n",
+ " 21: Amicable numbers 108 msec โ 31626 โ
\n",
+ " 22: Names scores 12 msec โ 871198282 โ
\n",
+ " 23: Non-abundant sums 472 msec โ 4179871 โ
\n",
+ " 24: Lexicographic permutations 6 msec โ 2783915460 โ
\n",
+ " 25: 1000-digit Fibonacci number 16 msec โ 4782 โ
\n",
+ " 26: Reciprocal cycles 270 msec โ 983 โ
\n",
+ " 27: Quadratic primes 234 msec โ -59231 โ
\n",
+ " 28: Number spiral diagonals 0 msec โ 669171001 โ
\n",
+ " 29: Distinct powers 2 msec โ 9183 โ
\n",
+ " 30: Digit fifth powers 190 msec โ 443839 โ
\n",
+ " 31: Coin sums 0 msec โ 73682 โ
\n",
+ " 32: Pandigital products 74 msec โ 45228 โ
\n",
+ " 33: Digit canceling fractions 3 msec โ 100 โ
\n",
+ " 34: Digit factorials 468 msec โ 40730 โ
\n",
+ " 35: Circular primes 382 msec โ 55 โ
\n",
+ " 36: Double-base palindromes 75 msec โ 872187 โ
\n",
+ " 37: Truncatable primes 0 msec โ 748317 โ
\n",
+ " 38: Pandigital multiples 43 msec โ 932718654 โ
\n",
+ " 39: Integer right triangles 8 msec โ 840 โ
\n",
+ " 40: Champernowne's constant 39 msec โ 210 โ
\n",
+ " 41: Pandigital prime 1 msec โ 7652413 โ
\n",
+ " 42: Coded triangle numbers 3 msec โ 162 โ
\n",
+ " 43: Sub-string divisibility 12 msec โ 16695334890 โ
\n",
+ " 44: Pentagon numbers 110 msec โ 5482660 โ
\n",
+ " 45: Triangular/pentagonal/hexagonal 18 msec โ 1533776805 โ
\n",
+ " 46: Goldbach's other conjecture 4 msec โ 5777 โ
\n",
+ " 47: Distinct primes factors 272 msec โ 134043 โ
\n",
+ " 48: Self powers 1 msec โ 9110846700 โ
\n",
+ " 49: Prime permutations 0 msec โ 296962999629 โ
\n",
+ " 50: Consecutive prime sum 3 msec โ 997651 โ
\n",
+ " 51: Prime digit replacements 315 msec โ 121313 โ
\n",
+ " 52: Permuted multiples 93 msec โ 142857 โ
\n",
+ " 53: Combinatoric selections 0 msec โ 4075 โ
\n",
+ " 54: Poker hands 8 msec โ 376 โ
\n",
+ " 55: Lychrel numbers 10 msec โ 249 โ
\n",
+ " 56: Powerful digit sum 44 msec โ 972 โ
\n",
+ " 57: Square root convergents 5 msec โ 153 โ
\n",
+ " 58: Spiral primes 43 msec โ 26241 โ
\n",
+ " 59: XOR decryption 771 msec โ 107359 โ
\n",
+ " 60: Prime pair sets 875 msec โ 26033 โ
\n",
+ " 61: Cyclical figurate numbers 3 msec โ 28684 โ
\n",
+ " 62: Cubic permutations 6 msec โ 127035954683 โ
\n",
+ " 63: Powerful digit counts 0 msec โ 49 โ
\n",
+ " 67: Maximum path sum II 4 msec โ 7273 โ
\n",
+ " 68: Magic 5-gon ring 23 msec โ 6531031914842725 โ
\n",
+ " 69: Totient maximum 108 msec โ 510510 โ
\n",
+ " 70: Totient Permutation 31 msec โ 8319823 โ
\n",
+ " 71: Ordered fractions 0 msec โ 428570 โ
\n",
+ " 72: Counting fractions 379 msec โ 303963552391 โ
\n",
+ " 73: Counting fractions in a range 756 msec โ 7295372 โ
\n",
+ " 74: Digit factorial chains 536 msec โ 402 โ
\n",
+ " 75: Singular integer right triangles 99 msec โ 161667 โ
\n",
+ " 76: Counting summations 1 msec โ 190569291 โ
\n",
+ " 77: Prime summations 1 msec โ 71 โ
\n",
+ " 78: Coin partitions 807 msec โ 55374 โ
\n",
+ " 79: Passcode Derivation 6 msec โ 73162890 โ
\n",
+ " 80: Square root digital expansion 1 msec โ 40886 โ
\n",
+ " 81: Path Sum, Two ways 3 msec โ 427337 โ
\n",
+ " 82: Path Sum, Three Ways 388 msec โ 260324 โ
\n",
+ " 83: Path Sum, Four Ways 6 msec โ 425185 โ
\n",
+ " 84: Monopoly odds 615 msec โ 101524 โ
\n",
+ " 85: Counting rectangles 807 msec โ 2772 โ
\n",
+ " 86: Cuboid Route 242 msec โ 1818 โ
\n",
+ " 87: Prime power triples 559 msec โ 1097343 โ
\n",
+ " 88: Product-sum Numbers 76 msec โ 7587457 โ
\n",
+ " 89: Roman numerals 4 msec โ 743 โ
\n",
+ " 90: Cube digit pairs 9 msec โ 1217 โ
\n",
+ " 91: Right Triangles with Integer Coordinates 769 msec โ 14234 โ
\n",
+ " 92: Square digit chains 34 msec โ 8581146 โ
\n",
+ " 93: Arithmetic expressions 39 msec โ 1258 โ
\n",
+ " 95: Amicable chains 569 msec โ 14316 โ
\n",
+ " 96: SuDoku 51 msec โ 24702 โ
\n",
+ " 97: Large non-Mersenne prime 0 msec โ 8739992577 โ
\n",
+ " 98: Anagramic squares 40 msec โ 18769 โ
\n",
+ " 99: Largest exponential 3 msec โ 709 โ
\n"
+ ]
+ }
+ ],
+ "source": [
+ "summary()"
+ ]
+ }
+ ],
+ "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
+}