diff --git a/src/18.ts b/src/18.ts index ffe84e52..315b388d 100644 --- a/src/18.ts +++ b/src/18.ts @@ -1,6 +1,7 @@ // Compute the maximum total from top to bottom of the triangle below. // // @remarks +// // Rather than calculating the path sums going down, look at the bottom row and // the row above it. // @@ -51,5 +52,6 @@ export default function compute() { triangle[i][j] += Math.max(triangle[i + 1][j], triangle[i + 1][j + 1]); } } + return triangle[0][0]; } diff --git a/src/22.rkt b/src/22.rkt deleted file mode 100644 index b224e8c2..00000000 --- a/src/22.rkt +++ /dev/null @@ -1,30 +0,0 @@ -#lang racket -(require rackunit) -(require srfi/1) -(require "lib/core.rkt") - - -(define names - ((compose (curry regexp-split ",") - (curryr string-replace "\"" "")) - (file->string "/data/22.txt"))) - -(define scores - (zipmap (map integer->char (range (char->integer #\A) - (add1 (char->integer #\Z)))) - (range 1 (add1 26)))) - -(define (score s) - (for/sum ((c (string->list s))) - (hash-ref scores c))) - -(define m - (zipmap (range 1 (add1 (length names))) - (map (curry score) (sort names string line.slice(1, -1)) + .sort((a, b) => a.localeCompare(b)); +} + +function getScores() { + const codes = _.range('A'.charCodeAt(0), 'Z'.charCodeAt(0) + 1).map(c => String.fromCharCode(c)); + const values = _.range(1, 27); + return new Map(_.zip(codes, values) as [[string, number]]); +} + +function getScore(name: string, scores: Map) { + return name.split('').reduce((a, b) => a + scores.get(b)!, 0); +} + +export default function compute() { + const scores = getScores(); + const names = getNames(); + + let result = 0; + for (let i = 0; i < names.length; i++) { + const name = names[i]; + const score = getScore(name, scores); + result += (i + 1) * score; + } + + return result; +} diff --git a/src/23.rkt b/src/23.rkt deleted file mode 100644 index fffb9cd0..00000000 --- a/src/23.rkt +++ /dev/null @@ -1,24 +0,0 @@ -#lang racket -(require rackunit) -(require "lib/number-theory.rkt") - - -(define limit 28123) - -(define numbers - (list->vector (range (add1 limit)))) - -(define abundants - (list->vector (filter (lambda (n) (> (sigma* n) n)) (range 1 (add1 limit))))) - -(for* ((i (range (vector-length abundants))) - (j (range i (vector-length abundants)))) - (let ((n (+ (vector-ref abundants i) (vector-ref abundants j)))) - (when (<= n limit) - (vector-set! numbers n 0)))) - -(define result - (for/sum ((n numbers) #:when (not (zero? n))) n)) - -(displayln result) -(check-equal? result 4179871) diff --git a/src/23.ts b/src/23.ts new file mode 100644 index 00000000..7db3f51a --- /dev/null +++ b/src/23.ts @@ -0,0 +1,23 @@ +import _ from 'lodash'; +import sigma from './core/sigma'; + +// Computes the sum of all the positive integers which cannot be written as the sum of two abundant numbers. +// +// See {@link https://projecteuler.net/problem=23} +export default function compute(limit: number) { + const abundants = _.range(1, limit + 1).filter(n => sigma(n) - n > n); + const numbers = _.range(0, limit + 1); + + for (let i = 0; i < abundants.length; i++) { + for (let j = i; j < abundants.length; j++) { + const sum = abundants[i] + abundants[j]; + if (sum <= limit) { + numbers[sum] = 0; + } else { + break; + } + } + } + + return numbers.reduce((a, b) => a + b, 0); +} diff --git a/src/67.rkt b/src/67.rkt deleted file mode 100644 index 08b9d70f..00000000 --- a/src/67.rkt +++ /dev/null @@ -1,42 +0,0 @@ -#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/src/67.ts b/src/67.ts new file mode 100644 index 00000000..39de4c06 --- /dev/null +++ b/src/67.ts @@ -0,0 +1,19 @@ +import slurp from './core/slurp'; + +// Computes the maximum path sum from the top to the bottom of a triangle. +// +// See {@link https://projecteuler.net/problem=67} +export default function compute() { + const triangle = slurp('67.txt') + .trim() + .split('\n') + .map(row => row.trim().split(' ').map(Number)); + + for (let i = triangle.length - 2; i >= 0; i--) { + for (let j = 0; j < triangle[i].length; j++) { + triangle[i][j] += Math.max(triangle[i + 1][j], triangle[i + 1][j + 1]); + } + } + + return triangle[0][0]; +} diff --git a/src/7.ts b/src/7.ts index fd0f4698..9c13001f 100644 --- a/src/7.ts +++ b/src/7.ts @@ -3,7 +3,8 @@ import primes from './core/primes'; // Computes the nth prime number. // // @remarks -// Cheats. Just uses a precomputed list of prime numbers. +// +// Problem cheats. Just uses a precomputed list of prime numbers. // // See {@link https://projecteuler.net/problem=7}. export function compute(n: number) { diff --git a/src/70.rkt b/src/70.rkt deleted file mode 100644 index 54b656a2..00000000 --- a/src/70.rkt +++ /dev/null @@ -1,69 +0,0 @@ -#lang racket -(require rackunit) -(require "lib/number-theory.rkt") - - -; 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))) - -; 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) diff --git a/src/70.ts b/src/70.ts new file mode 100644 index 00000000..8e414105 --- /dev/null +++ b/src/70.ts @@ -0,0 +1,69 @@ +import primes from './core/primes'; + +// Computes the value of n for which φ((n) is a permutation of n and the ratio n/φ(n) produces a minimum. +// +// See {@link https://projecteuler.net/problem=70} +export default function compute(limit: number) { + const pairs = getSemiPrimePairs(getLimit(limit)) + .map(pair => transformPair(pair)) + .filter(pair => isWithinLimit(pair, limit)) + .filter(pair => isPermutation(pair)); + + let minRatio = limit; + let result = 0; + + for (const [phi, n] of pairs) { + const ratio = n / phi; + if (ratio < minRatio) { + minRatio = ratio; + result = n; + } + } + + return result; +} + +// 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, +// totient(p * q) = totient(p) * totient(q). Therefore, the totient of a semi-prime is simply (p - 1) * (q - 1). +function totient(p: number, q: number): number { + return (p - 1) * (q - 1); +} + +// Checks if two positive integers' digits are permutations of each other. +function isPermutation(pair: [number, number]): boolean { + const [m, n] = pair; + return m.toString().split('').sort().join('') === n.toString().split('').sort().join(''); +} + +function getLimit(limit: number): number { + return 2 * (Math.floor(Math.sqrt(limit)) + 1); +} + +function isWithinLimit(pair: [number, number], limit: number): boolean { + return pair[1] < limit; +} + +function getSemiPrimePairs(limit: number): [number, number][] { + const ps = primes().filter(p => p < limit); + const pairs: [number, number][] = []; + + for (let i = 0; i < ps.length; i++) { + for (let j = i; j < ps.length; j++) { + const p = ps[i]; + const q = ps[j]; + pairs.push([p, q]); + } + } + + return pairs; +} + +// Creates totient to semi-prime pairs. +function transformPair(factors: [number, number]): [number, number] { + const [p, q] = factors; + const n = p * q; + return [totient(p, q), n]; +} diff --git a/src/8.ts b/src/8.ts index 79df4df7..d452cccf 100644 --- a/src/8.ts +++ b/src/8.ts @@ -4,6 +4,7 @@ import slurp from './core/slurp'; // Compute the largest product of of `window` consecutive digits in the given data. // // @remarks +// // Can be solved with a sliding window algorithm. We just have to take care to note the number of zeroes in the window; // if a window has any zeroes in it, thet product of that window will be zero. At each step, we only compute the // product if we have no zeroes in the window. diff --git a/src/core/slurp.ts b/src/core/slurp.ts index 5101acd0..2d22f3a1 100644 --- a/src/core/slurp.ts +++ b/src/core/slurp.ts @@ -2,5 +2,6 @@ import fs from 'fs'; import path from 'path'; export default function slurp(name: string): string { - return fs.readFileSync(path.join('data', name), 'utf8').toString(); + const file = path.join('data', name); + return fs.readFileSync(file, 'utf8').toString(); } diff --git a/test/22.test.ts b/test/22.test.ts new file mode 100644 index 00000000..9df60adc --- /dev/null +++ b/test/22.test.ts @@ -0,0 +1,7 @@ +import compute from '../src/22'; + +describe('name scores', () => { + test('problem 22', async () => { + expect(compute()).toBe(871198282); + }); +}); diff --git a/test/23.test.ts b/test/23.test.ts new file mode 100644 index 00000000..cce81cd2 --- /dev/null +++ b/test/23.test.ts @@ -0,0 +1,7 @@ +import compute from '../src/23'; + +describe('non-abundant sums', () => { + test('problem 23', async () => { + expect(compute(28123)).toBe(4179871); + }); +}); diff --git a/test/67.test.ts b/test/67.test.ts new file mode 100644 index 00000000..f91e4ec0 --- /dev/null +++ b/test/67.test.ts @@ -0,0 +1,7 @@ +import compute from '../src/67'; + +describe('maximum path sum ii', () => { + test('problem 67', async () => { + expect(compute()).toBe(7273); + }); +}); diff --git a/test/70.test.ts b/test/70.test.ts new file mode 100644 index 00000000..3f1febc7 --- /dev/null +++ b/test/70.test.ts @@ -0,0 +1,7 @@ +import compute from '../src/70'; + +describe('totient permutation', () => { + test('problem 70', async () => { + expect(compute(1e7)).toBe(8319823); + }); +});