From 7b83f1a136cafa2b0a32e0e58307e1335cc7ca1b Mon Sep 17 00:00:00 2001 From: Min Huang Date: Sat, 22 Mar 2025 00:45:33 -0700 Subject: [PATCH 1/3] update problem 3 --- docs/problem-3.md | 66 +++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/docs/problem-3.md b/docs/problem-3.md index 7646af3d..99297b38 100644 --- a/docs/problem-3.md +++ b/docs/problem-3.md @@ -1,5 +1,5 @@ --- -title: "Problem 3: Largest prime factor" +title: "Problem 3: Largest Prime Factor" layout: post mathjax: true --- @@ -7,6 +7,7 @@ mathjax: true # Largest prime factor ## Problem + Here is [problem 3](https://projecteuler.net/problem=3): The prime factors of 13195 are 5, 7, 13 and 29. @@ -14,6 +15,7 @@ The prime factors of 13195 are 5, 7, 13 and 29. What is the largest prime factor of the number 600851475143? ## Solution + [Trial division](https://en.wikipedia.org/wiki/Trial_division) works just fine for this problem, but the following solution in Scala demonstrations [Fermat's factorization method](https://en.wikipedia.org/wiki/Fermat%27s_factorization_method) (note that Fermat's method can be slower than trial division). It works like this: suppose you want to find factors of some integer $$n$$, and it can be expressed as the difference of two squares: $$n=a^2-b^2$$ @@ -26,36 +28,38 @@ Because [all odd numbers can be written as the difference of two squares](https: If we end up with a result that $$n=(a+b)(a-b)=(1)(n)$$, we can conclude that $$n$$ is prime and stop. +NOTE: the actual solution takes the easy path out and uses `sympy`. + ## Code -```scala -import scala.math.ceil -import scala.math.sqrt -import scala.math.round - - -def fermatFactors(n: Long): Set[Long] = { - if (n % 2 == 0) return Set(2, n / 2) - var a = ceil(sqrt(n)) - var b = a * a - n - var s = sqrt(b) - while (s != round(s)) { - a += 1 + +```python +import math + +def fermat_factors(n: int) -> set[int]: + if n % 2 == 0: + return {2, n // 2} + + a = math.ceil(math.sqrt(n)) b = a * a - n - s = sqrt(b) - } - Set(a - s, a + s).map(_.toLong) -} - -def primeFactors(n: Long): Set[Long] = { - val factors = fermatFactors(n) - if (factors contains 1) - factors - 1 - else - factors.map(f => primeFactors(f)).reduceLeft(_ ++ _) -} - -val result = primeFactors(600851475143L).reduceLeft(_ max _) - -println(result) -assert(result == 6857) + s = math.sqrt(b) + + while s != round(s): + a += 1 + b = a * a - n + s = math.sqrt(b) + + s = int(s) + return {a - s, a + s} + +def prime_factors(n: int) -> set[int]: + factors = fermat_factors(n) + if 1 in factors: + return factors - {1} + else: + result = set() + for f in factors: + result |= prime_factors(f) + return result + +assert max(prime_factors(600851475143)) == 6857 ``` From 7944ef565d5ad27696fd42d128ff392a23044c99 Mon Sep 17 00:00:00 2001 From: Min Huang Date: Sat, 22 Mar 2025 00:50:10 -0700 Subject: [PATCH 2/3] problem 15 --- docs/problem-15.md | 32 ++++++++++---------------------- docs/problem-3.md | 2 +- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/docs/problem-15.md b/docs/problem-15.md index a6df1646..c42c4bfc 100644 --- a/docs/problem-15.md +++ b/docs/problem-15.md @@ -1,5 +1,5 @@ --- -title: "Problem 15: Lattice paths" +title: "Problem 15: Lattice Paths" layout: post mathjax: true --- @@ -7,6 +7,7 @@ mathjax: true # Lattice paths ## Problem + Here is [problem 15](https://projecteuler.net/problem=15): Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down, there are exactly 6 routes to the bottom right corner. @@ -14,30 +15,17 @@ Starting in the top left corner of a 2×2 grid, and only being able to move to t How many such routes are there through a 20×20 grid? ## Solution + This problem can be viewed as a [multi-permutation](http://en.wikipedia.org/wiki/Multinomial_coefficient) of 20 L's and 20 R's where L means "go left" and R means "go right". This way, no searching is needed and the solution can be calculated directly as: $$\frac{(20 + 20)!}{20!20!}$$ +However, because $$\frac{40!}{20! \cdot (40 - 20)!}$$ is just $$\binom{40}{20}$$, it can be calculated directly via `math.comb`. + ## Code -```racket -#!/usr/bin/env racket -#lang racket -(require rackunit) - - -(define (factorial n) - (define (factorial* i acc) - (if (= i 0) - acc - (factorial* (sub1 i) (* i acc)))) - (factorial* n 1)) - -(define result - (let* ((n (factorial (+ 20 20))) - (d (* (factorial 20) (factorial 20))) - (r (/ n d))) - r)) - -(displayln result) -(check-equal? result 137846528820) + +```python +import math + +assert math.comb(40, 2) == 137846528820 ``` diff --git a/docs/problem-3.md b/docs/problem-3.md index 99297b38..82007c7a 100644 --- a/docs/problem-3.md +++ b/docs/problem-3.md @@ -56,7 +56,7 @@ def prime_factors(n: int) -> set[int]: if 1 in factors: return factors - {1} else: - result = set() + result: set[int] = set() for f in factors: result |= prime_factors(f) return result From 7cb9149e45970ca5cd76fd90c7343eac904efe83 Mon Sep 17 00:00:00 2001 From: Min Huang Date: Sat, 22 Mar 2025 01:05:17 -0700 Subject: [PATCH 3/3] update docs --- docs/problem-120.md | 22 ------------ docs/problem-15.md | 31 ---------------- docs/problem-188.md | 29 --------------- docs/problem-24.md | 82 ------------------------------------------ docs/problem-26.md | 34 +++--------------- docs/problem-27.md | 40 +++------------------ docs/problem-3.md | 65 --------------------------------- docs/problem-67.md | 55 +++------------------------- docs/problem-69.md | 26 +++----------- docs/problem-70.md | 87 +++------------------------------------------ project_euler/15.py | 11 ++++-- project_euler/24.py | 12 +++++-- 12 files changed, 40 insertions(+), 454 deletions(-) delete mode 100644 docs/problem-120.md delete mode 100644 docs/problem-15.md delete mode 100644 docs/problem-188.md delete mode 100644 docs/problem-24.md delete mode 100644 docs/problem-3.md diff --git a/docs/problem-120.md b/docs/problem-120.md deleted file mode 100644 index 7e635433..00000000 --- a/docs/problem-120.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "Problem 188: Square remainders" -layout: post -mathjax: true ---- - -# Square remainders - -## Problem -Here is [problem 120](https://projecteuler.net/problem=120): - -Let $$r$$ be the remainder when $$(a−1)^{n} + (a+1)^{n}$$ is divided by $$a^{2}$$. - -For example, if $$a=7$$ and $$n=3$$, then $$r=42$$: $$63+83 = 728 \equiv 42 \pmod 49$$. And as $$n$$ varies, so too will $$r$$, but for $$a=7$$ it turns out that $$r_{max} = 42$$. - -For $$3 \leq a \leq 1000$$, find $$\sum r_{max}$$. - -## Solution -WIP - -## Code -WIP diff --git a/docs/problem-15.md b/docs/problem-15.md deleted file mode 100644 index c42c4bfc..00000000 --- a/docs/problem-15.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: "Problem 15: Lattice Paths" -layout: post -mathjax: true ---- - -# Lattice paths - -## Problem - -Here is [problem 15](https://projecteuler.net/problem=15): - -Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down, there are exactly 6 routes to the bottom right corner. - -How many such routes are there through a 20×20 grid? - -## Solution - -This problem can be viewed as a [multi-permutation](http://en.wikipedia.org/wiki/Multinomial_coefficient) of 20 L's and 20 R's where L means "go left" and R means "go right". This way, no searching is needed and the solution can be calculated directly as: - -$$\frac{(20 + 20)!}{20!20!}$$ - -However, because $$\frac{40!}{20! \cdot (40 - 20)!}$$ is just $$\binom{40}{20}$$, it can be calculated directly via `math.comb`. - -## Code - -```python -import math - -assert math.comb(40, 2) == 137846528820 -``` diff --git a/docs/problem-188.md b/docs/problem-188.md deleted file mode 100644 index 9f43d3e4..00000000 --- a/docs/problem-188.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: "Problem 188: The hyperexponentiation of a number" -layout: post -mathjax: true ---- - -# The hyperexponentiation of a number - -## Problem -Here is [problem 188](https://projecteuler.net/problem=188): - -The *hyperexponentiation* or *tetration* of a number $$a$$ by a positive integer $$b$$, denoted by $$a \uparrow \uparrow b$$ or $$^{b}a$$, is recursively defined by: - -$$ -\begin{align*} -&a \uparrow \uparrow 1 = a \\ -&a \uparrow \uparrow (k+1) = a \cdot (a \uparrow \uparrow k) \\ -\end{align*} -$$ - -Thus we have e.g. $$3 \uparrow \uparrow 2 = 3^{3} = 27$$, hence $$3 \uparrow \uparrow 3 = 3^{27} = 7625597484987$$ and $$3 \uparrow \uparrow 4$$ is roughly $$103.6383346400240996 \times 10^{12}$$. - -Find the last 8 digits of $$1777 \uparrow \uparrow 1855$$. - -## Solution -WIP - -## Code -WIP diff --git a/docs/problem-24.md b/docs/problem-24.md deleted file mode 100644 index 50570fcf..00000000 --- a/docs/problem-24.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: "Problem 24: Lexicographic permutations" -layout: post -mathjax: true ---- - -# Lexicographic permutations - -## Problem -Here is [problem 24](https://projecteuler.net/problem=24): - -A permutation is an ordered arrangement of objects. For example, 3124 is one possible permutation of the digits 1, 2, 3 and 4. If all of the permutations are listed numerically or alphabetically, we call it lexicographic order. The lexicographic permutations of 0, 1 and 2 are: - -> 012 021 102 120 201 210 - -What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9? - -## Solution -Start with the smallest possible digit (0 in this case), then find the number of permutations of the remaining digits. If this number is less than 1000000, then 0 cannot be the first digit in the 1000000th permutation. - -Try 1 instead, and count the number of permutations. Keep going with 2, 3, 4, etc until the number of permutations exceeds 1000000. Suppose that fixing the first digit at $$k$$ results in more than 1000000 permutations; this means the first digit is fixed at $$k-1$$, and the second digit must be examined by this method. - -## Code -```scala -val digits = List(0,1,2,3,4,5,6,7,8,9) -val target = 1000000 - -def fact(n: Int): Int = if (n == 0 || n == 1) 1 else (2 to n).reduceLeft(_ * _) - -def compute(list: List[Int]): List[Int] = { - def recur(list: List[Int], current: Int): List[Int] = { - // Permutations of the remaining digits - val permutations = fact(list.length - 1) - // Index of a digit to be fixed, in lexicographical order - val index = (target - current) / permutations - val digit = list(index) - // How many permutations seen so far + how many available after fixing a - // digit - val next = current + index * permutations - list.length match { - // If we reached the target, leave the last 2 digits in ascending order - case 2 if target - current == 0 => list - // If we are one off the target, flip them so they are in descending order - case 2 if target - current == 1 => list.reverse - case _ => digit :: - recur(list.filterNot(_ == digit), next) - } - } - recur(list, 1) -} - -val result = compute(digits).mkString("").toLong - -println(result) -assert(result == 2783915460L) -``` - -You can also cheat in Racket by asking the standard library to generate permutations for you. - -```racket -#!/usr/bin/env racket -#lang racket -(require rackunit) - - -(define (list->number lst) - ((compose string->number - (curry apply string-append) - (curry map number->string)) - lst)) - -(define result - ((compose (curryr vector-ref (sub1 1000000)) - list->vector - (curryr sort <) - (curry map list->number) - permutations) - (range 10))) - -(displayln result) -(check-equal? result 2783915460) -``` diff --git a/docs/problem-26.md b/docs/problem-26.md index a858ad24..f8206de5 100644 --- a/docs/problem-26.md +++ b/docs/problem-26.md @@ -1,12 +1,13 @@ --- -title: "Problem 26: Reciprocal cycles" +title: "Problem 26: Reciprocal Cycles" layout: post mathjax: true --- -# Reciprocal cycles +# Reciprocal Cycles ## Problem + Here is [problem 26](https://projecteuler.net/problem=26): A unit fraction contains 1 in the numerator. The decimal representation of the unit fractions with denominators 2 to 10 are given: @@ -30,6 +31,7 @@ Where $$0.1\overline{6}$$ means $$0.16666...$$, and has a 1-digit recurring cycl Find the value of $$d \le 1000$$ for which $$\dfrac{1}{d}$$ contains the longest recurring cycle in its decimal fraction part. ## Solution + It is better to search searching for cycles starting from $$d = 999$$ and decrementing because the longest cycle will occur when $$d$$ is largest. A number $$\dfrac{1}{n}$$ cannot have more than $$n$$ repeating digits in its decimal expansion. To illustrate this, consider what is happening when you do long division to calculate $$\dfrac{1}{7}$$: First you divide 1.0 by 7 and find the remainder. Computationally, this is the same as dividing 10 by 7, then multiplying the remainder by 10, dividing by 7 again, and so on. See the sequence of computations for calculating 1/7 below, written in this manner: @@ -65,31 +67,3 @@ $$ $$ As you can see, at the next to last step, the digits start to repeat. In fact, the number of repeating digits is given by $$e = ord_{10}n$$, where $$e$$ is the smallest positive integer such that $$10^e \equiv 1 \pmod{n}$$ (this concept is called the [multiplicative order](https://en.wikipedia.org/wiki/Multiplicative_order). The preceding is not a proof of this fact, but it was enough to start searching for solutions near 999. - -## Code -```racket -#!/usr/bin/env racket -#lang racket -(require (only-in rackunit check-equal?)) -(require (only-in "lib/number-theory.rkt" divides? ord)) - - -(define numbers - (filter (lambda (n) (and (not (divides? 2 n)) (not (divides? 5 n)))) - (range 999 1 -1))) - -(define (max-period best numbers) - (if (empty? numbers) - best - (let* ((n (first numbers)) - (t (ord 10 n))) - (cond ((= t (sub1 n)) (cons n t)) - ((> t (cdr best)) (max-period (cons n t) (rest numbers))) - (else (max-period best (rest numbers))))))) - -(define result - (car (max-period (cons 0 0) numbers))) - -(displayln result) -(check-equal? result 983) -``` diff --git a/docs/problem-27.md b/docs/problem-27.md index 3ffab9df..cc92666d 100644 --- a/docs/problem-27.md +++ b/docs/problem-27.md @@ -1,12 +1,13 @@ --- -title: "Problem 27: Quadratic primes" +title: "Problem 27: Quadratic Primes" layout: post mathjax: true --- -# Quadratic primes +# Quadratic Primes ## Problem + Here is [problem 27](https://projecteuler.net/problem=27): Euler discovered the remarkable quadratic formula: @@ -26,6 +27,7 @@ $$n^2+an+b$$ Find the product of the coefficients, $$a$$ and $$b$$, for the quadratic expression that produces the maximum number of primes for consecutive values of $$n$$, starting with $$n=0$$. ## Solution + We can brute force a solution by computing consecutive primes for every quadratic in the solution space of values for $$a$$ and $$b$$. However, we can narrow the search space by looking at a few constraints. First, notice that if $$f(0)$$ isn't prime, we wouldn't have a very long prime sequence at all, so consider only values of $$b$$ that are prime. @@ -37,37 +39,3 @@ $$f(b) = b^2+ab+b$$ $$f(b) = b(b+a+1)$$ This means that when computing consecutive primes for values of $$n$$, we only need to consider values in the range $$0 \leq n \le b$$. - -## Code -With these two constraints, and a pre-computed list of primes, the following Racket solution finds $$a=-61$$ and $$b=971$$ quite quickly: - -```racket -#!/usr/bin/env racket -#lang racket -(require rackunit) -(require "lib/core.rkt") -(require "lib/number-theory.rkt") - - -(define ps - (filter prime? (range 1000))) - -(define (f n a b) - (+ (* n n) (* a n) b)) - -(define (primes-count a b) - (length (takef (range b) (compose prime? (curryr f a b))))) - -(define m - (for*/hash ((b (append ps (map (curry * -1) ps))) - (a (range -1000 1001))) - (let ((c (primes-count a b))) - (values c (cons a b))))) - -(define result - (let ((v (hash-ref m (apply max (hash-keys m))))) - (* (car v) (cdr v)))) - -(displayln result) -(check-equal? result -59231) -``` diff --git a/docs/problem-3.md b/docs/problem-3.md deleted file mode 100644 index 82007c7a..00000000 --- a/docs/problem-3.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: "Problem 3: Largest Prime Factor" -layout: post -mathjax: true ---- - -# Largest prime factor - -## Problem - -Here is [problem 3](https://projecteuler.net/problem=3): - -The prime factors of 13195 are 5, 7, 13 and 29. - -What is the largest prime factor of the number 600851475143? - -## Solution - -[Trial division](https://en.wikipedia.org/wiki/Trial_division) works just fine for this problem, but the following solution in Scala demonstrations [Fermat's factorization method](https://en.wikipedia.org/wiki/Fermat%27s_factorization_method) (note that Fermat's method can be slower than trial division). It works like this: suppose you want to find factors of some integer $$n$$, and it can be expressed as the difference of two squares: - -$$n=a^2-b^2$$ - -Then $$n$$ can easily be factored [like so](https://en.wikipedia.org/wiki/Difference_of_two_squares): - -$$n=(a+b)(a-b)$$ - -Because [all odd numbers can be written as the difference of two squares](https://math.stackexchange.com/questions/263101/prove-every-odd-integer-is-the-difference-of-two-squares), we can test out values of $$a$$ starting at $$a=\sqrt{n}$$. If $$a^2-n=b$$ is a perfect square, we can stop. Otherwise increment $$a$$ by 1 and keep testing. - -If we end up with a result that $$n=(a+b)(a-b)=(1)(n)$$, we can conclude that $$n$$ is prime and stop. - -NOTE: the actual solution takes the easy path out and uses `sympy`. - -## Code - -```python -import math - -def fermat_factors(n: int) -> set[int]: - if n % 2 == 0: - return {2, n // 2} - - a = math.ceil(math.sqrt(n)) - b = a * a - n - s = math.sqrt(b) - - while s != round(s): - a += 1 - b = a * a - n - s = math.sqrt(b) - - s = int(s) - return {a - s, a + s} - -def prime_factors(n: int) -> set[int]: - factors = fermat_factors(n) - if 1 in factors: - return factors - {1} - else: - result: set[int] = set() - for f in factors: - result |= prime_factors(f) - return result - -assert max(prime_factors(600851475143)) == 6857 -``` diff --git a/docs/problem-67.md b/docs/problem-67.md index 65d4ea42..c6ec7446 100644 --- a/docs/problem-67.md +++ b/docs/problem-67.md @@ -1,12 +1,13 @@ --- -title: "Problem 67: Maximum path sum II" +title: "Problem 67: Maximum Path Sum II" layout: post mathjax: true --- -# Maximum path sum II +# Maximum Path Sum II ## Problem + Here is [problem 67](https://projecteuler.net/problem=67). By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23. @@ -25,6 +26,7 @@ Find the maximum total from top to bottom in [triangle.txt](https://github.com/r NOTE: This is a much more difficult version of Problem 18. It is not possible to try every route to solve this problem, as there are $$2^{99}$$ altogether! If you could check one trillion ($$10^{12}$$) routes every second it would take over twenty billion years to check them all. There is an efficient algorithm to solve it. ;o) ## Solution + Rather than brute force all possible paths starting from the top, start from the bottom row. To illustrate the algorithm, consider a smaller problem: ``` @@ -47,52 +49,3 @@ Once you have determined this, it's no longer necessary to consider the last row ...since from element 2, the largest sum of each sub-path is 7, and from element 3, the largest sum of each sub-path is 9. You can apply this logic repeatedly to reduce a triangle of $$n$$ rows down to $$n-1$$ rows. - -## Code -With this algorithm, the running time is only $$O(n)$$ where $$n$$ is the number of rows of the triangle: - -```racket -#!/usr/bin/env racket -#lang racket -(require rackunit) - - -(define (2dvector-num-rows a) (vector-length a)) - -(define (2dvector-num-cols a i) - (let ((v (vector-ref a i))) - (length (takef (vector->list v) (lambda (e) (not (= e 0))))))) - -(define (2dvector-ref a i j) - (vector-ref (vector-ref a i) j)) - -(define (2dvector-set! a i j x) - (vector-set! (vector-ref a i) j x)) - -(define input (string-trim (file->string "../data/67.txt"))) - -(define (string->row s) - (map string->number (regexp-split #px" " s))) - -(define data - ((compose - list->vector - (curry map list->vector) - (curry map string->row) - (curry map string-trim) - (curry regexp-split #px"\n")) - input)) - -(for* ((i (range (- (2dvector-num-rows data) 2) (sub1 0) -1)) - (j (range 0 (2dvector-num-cols data i)))) - (let* ((current (2dvector-ref data i j)) - (left (2dvector-ref data (add1 i) j)) - (right (2dvector-ref data (add1 i) (add1 j))) - (bigger (max left right))) - (2dvector-set! data i j (+ current bigger)))) - -(define result (2dvector-ref data 0 0)) - -(displayln result) -(check-equal? result 7273) -``` diff --git a/docs/problem-69.md b/docs/problem-69.md index 0f0d0c44..f004b181 100644 --- a/docs/problem-69.md +++ b/docs/problem-69.md @@ -1,12 +1,13 @@ --- -title: "Problem 69: Totient maximum" +title: "Problem 69: Totient Maximum" layout: post mathjax: true --- -# Totient maximum +# Totient Maximum ## Problem + Here is [problem 69](https://projecteuler.net/problem=69): Euler's Totient function, $$\varphi(n)$$ [sometimes called the phi function], is used to determine the number of numbers less than $$n$$ which are relatively prime to $$n$$. For example, as 1, 2, 4, 5, 7, and 8, are all less than 9 and relatively prime to 9, $$\varphi(9)=6$$. @@ -28,6 +29,7 @@ It can be seen that $$n=6$$ produces a maximum $$n/\varphi(n)$$ for $$n \leq 10$ Find the value of $$n \leq 1,000,000$$ for which $$n/\varphi(n)$$ is a maximum. ## Solution + From the [wikipedia page on the totient function](https://en.wikipedia.org/wiki/Euler%27s_totient_function): $$\dfrac{n}{\varphi(n)} = \dfrac{1}{n\prod_{p|n}(1 - \dfrac{1}{p})}$$ @@ -45,23 +47,3 @@ The product of $$m$$ consecutive primes is known as the [primorial](http://en.wi $$p_{m}\# = \prod_{k=1}^m p_{k}$$ With a list of primes it can be calculated that $$p_{7}\# = 510510$$ and $$p_{8}\# = 9699690$$, the latter of which is larger than our limit of 1000000, so $$p_{7}\#$$ is the answer. - -## Code -```clojure -(load-file "lib/number-theory.clj") -(use '[lib.number-theory :only (load-primes)]) - - -(def primes (load-primes "../data/primes.txt")) - -(defn f [acc ps] - (let [n (* acc (first ps))] - (if (> n 1000000) - acc - (recur n (rest ps))))) - -(def result (f 1 primes)) - -(println result) -(assert (= result 510510)) -``` diff --git a/docs/problem-70.md b/docs/problem-70.md index ee7e8bd8..f162b63a 100644 --- a/docs/problem-70.md +++ b/docs/problem-70.md @@ -1,12 +1,13 @@ --- -title: "Problem 70: Totient permutation" +title: "Problem 70: Totient Permutation" layout: post mathjax: true --- -# Totient permutation +# Totient Permutation ## Problem + Here is [problem 70](https://projecteuler.net/problem=70): Euler's Totient function, $$\varphi(n)$$ [sometimes called the phi function], is used to determine the number of positive numbers less than or equal to $$n$$ which are relatively prime to $$n$$. For example, as 1, 2, 4, 5, 7, and 8, are all less than nine and relatively prime to nine, $$\varphi(9)=6$$. @@ -18,6 +19,7 @@ Interestingly, $$\varphi(87109)=79180$$, and it can be seen that 87109 is a perm Find the value of $$n$$, $$1 \le n \le 10^{7}$$, for which $$\varphi(n)$$ is a permutation of $$n$$ and the ratio $$\frac{n}{\varphi(n)}$$ produces a minimum. ## Solution + In order for the ratio $$\frac{n}{\varphi(n)}$$ to be at a minimum, $$\varphi(n)$$ needs to be at as large as possible. Because $$\varphi(n)$$ is the number of positive integers relatively prime (or [coprime](https://en.wikipedia.org/wiki/Coprime_integers)), $$\varphi(n)$$ is at a maximum when $$n$$ is a prime. This is because a prime number $$p$$ cannot have any other divisors except itself, so every positive integer less than $$p$$ is coprime to it, and hence $$\varphi(p)=p-1$$. @@ -26,83 +28,4 @@ Knowing this, one could generate primes up to $$10^{7}$$, compute the ratio $$\f The next best way to maximize $$\varphi(n)$$ is to consider $$n$$'s that are [semi-primes](https://en.wikipedia.org/wiki/Semiprime) (that is, numbers that are the product of exactly two primes). In this case, $$n$$ would have a smaller totient than if it were prime, but still there would be more coprime numbers less than $$n$$ than if $$n$$ were, say, composed of the product of 3 or more primes. -## Code -The following Racket code generates semi-prime pairs where the factors $$p$$ and $$q$$ are around the vicinity of $$\sqrt{10^{7}}$$ as we want $$n = p \cdot q$$. - -```racket -#!/usr/bin/env racket -#lang racket -(require rackunit) -(require srfi/1) -(require srfi/26) - - -; Search around the radius of (sqrt 1e7), and generate all possible semi-prime -; pairs "around" that vicinity. -(define limit (inexact->exact 1e7)) - -(define limit* (* 2 (add1 (integer-sqrt limit)))) - -(define ps - (filter (curryr < limit*) (file->list "../data/primes.txt"))) - -(define bs - (combinations ps 2)) - -; This is a special totient function that optimizes the computation of totient -; for a semi-prime whose factors are p and q. -; -; For a prime number, the totient is simply (sub1 p). Because the totient is -; a multiplicative function, (phi (* p q)) is the same as (* (phi p) (phi q)). -(define (totient* p q) - (* (sub1 p) (sub1 q))) - -; Converts a (base 10) number to a list. Example: 1234 -> '(1 2 3 4). -(define (number->list n) - (unfold-right zero? (cut remainder <> 10) (cut quotient <> 10) n)) - -; Checks if two positive integers' digits are permutations of each other. -(define (digits-permutation? m n) - (let ((m* (sort (number->list m) <)) - (n* (sort (number->list n) <))) - (equal? m* n*))) - -(define (digits-permutation*? pair) - (let* ((m (car pair)) - (n (cdr pair))) - (digits-permutation? m n))) - -; Defines pair of totient(n) and the prime factors of n (p and q). -(define (make-totient-to-semi-prime-pair factors) - (let* ((p (car factors)) - (q (cadr factors)) - (n (* p q))) - (cons (totient* p q) n))) - -; Filter out semiprimes > limit that aren't digit permutations with the totient. -(define (within-limit? pair) - (let ((n (cdr pair))) - (<= n limit))) - -(define totient-to-semi-prime-pairs - ((compose (curry filter digits-permutation*?) - (curry filter within-limit?) - (curry map make-totient-to-semi-prime-pair)) - bs)) - -; Of the candidates that are digit permutations and where the ratio is at the -; max, find the largest value of phi(n) * n. -(define min-ratio limit) -(define result 0) - -(for ((pair totient-to-semi-prime-pairs)) - (let* ((phi (car pair)) - (n (cdr pair)) - (ratio (/ n phi))) - (when (< ratio min-ratio) - (set! min-ratio ratio) - (set! result n)))) - -(displayln result) -(check-equal? result 8319823) -``` +The code generates semi-prime pairs where the factors $$p$$ and $$q$$ are around the vicinity of $$\sqrt{10^{7}}$$ as we want $$n = p \cdot q$$. diff --git a/project_euler/15.py b/project_euler/15.py index 3a0cbeea..9dc15a82 100644 --- a/project_euler/15.py +++ b/project_euler/15.py @@ -12,8 +12,15 @@ def run(): # downward movement as a 'D' and the rightward movement as an 'R', then the number of routes to the bottom right is # equal to the number of permutations of a 'word' with 20 D's and 20 R's. # - # This can be viewed as a multiset permutation whose cardinality is the multinomial coefficient: (20 + 20)! / 20!20! - # You can compute this as math.comb(20 + 20, 20). + # This can be viewed as a multiset permutation whose cardinality is the multinomial coefficient: + # + # (20 + 20)! / 20!20! = 40! / 20!20! + # + # You can compute this as math.comb(40, 20) because: + # + # math.comb(n, k) = n! / (k! * (n - k)!) + # + # ...and in this case, n = 40 and k = 20. return math.comb(20 + 20, 20) diff --git a/project_euler/24.py b/project_euler/24.py index 09c80fc5..d739f3af 100644 --- a/project_euler/24.py +++ b/project_euler/24.py @@ -4,17 +4,25 @@ # # See https://projecteuler.net/problem=24 from itertools import permutations +from operator import itemgetter from typing import cast from toolz import curried, pipe +def tuple2int(t: tuple[int, ...]) -> int: + cs = map(str, t) + s = "".join(cs) + return int(s) + + def run() -> int: result = pipe( range(10), permutations, - curried.map(lambda xs: int("".join(map(str, xs)))), + curried.map(tuple2int), sorted, - lambda xs: xs[999_999], + # Same thing as lambda xs: xs[999_999] + itemgetter(999_999), ) return cast(int, result)