Tag Archive for "projecteuler" tag

Problem 4 ver. 4: optimization

December 18th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the largest palindrome made from the product of two 3-digit numbers.

And the last scratch for now. It is possible to prove that 11 divides a palindromic number. Indeed,

N = \sum_{i=0}^k (10^{2k-i+1} d_i + 10^i d_i) = \sum 10^i d_i (10^{2(k-i)+1} + 1) = \sum 10^i d_i m_i

and here m_i is a multiple of 11 (divisibility by 11 criterion).

The factor 11 can belong to a - and in this case we step just 1 in b. But if 11 doesn't divide a, then we can increase b by 11 each time.

  1.         (define (find-largest-palindrome-using-11 k)
  2.            (let ([m (- (^ 10 k) 1)] [m/10 (^ 10 (- k 1))])
  3.              (let ([m-11 (* 11 (div m 11))])
  4.                (define (iter a b largest-palindrome)
  5.                  (if (< a m/10) largest-palindrome
  6.                      (let ([step (if (= 0 (mod a 11)) 1 11)]
  7.                            [next-a-11? (= 0 (mod (- a 1) 11))])
  8.                        (if (< b m/10) (iter (- a 1) (if next-a-11? m m-11) largest-palindrome)
  9.                            (let ([n (* a b)])
  10.                              (if (<= n largest-palindrome) (iter (- a 1) m largest-palindrome)
  11.                                  (if (palindrome-number? n) (iter (- a 1) m n)
  12.                                      (iter a (- b step) largest-palindrome))))))))
  13.                (iter m m 0))))

This speeds up the previous result around ten times, leaving an asymptotic behavior the same. The memory use is the same O(1).

Let's look at results:
k = 2 => N = 9009
k = 3 => N = 906609
k = 4 => N = 99000099
k = 5 => N = 9966006699
k = 6 => N = 999000000999
k = 7 => N = 99956644665999
k = 8 => N = 9999000000009999
...
We could improve our algo drastically, if proven that the sought-for palindrome is less or equal n - \sqrt n (and mirrored). I have the feeling that for even k it is equal. But I don't know how to prove it. (I calculated for k = 10 and this does not hold, N = 99999834000043899999).

Problem 4 ver. 3: optimization

December 17th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the largest palindrome made from the product of two 3-digit numbers.

An author, however, advises a simpler approach. As we are looking for a palindrome a*b, let's iterate a and b in a top-down direction. After finding some palindrome, impose it as a top boundary for palindromes, that is, iterating in the inner loop for b, we stop when a*b cannot be large than that anymore. If we found a new palindrome, it will replace the boundary. Stop condition is finishing the outer loop in a, i.e. when it drops to 2-digits number (k-1, generally speaking).

  1.         (define (find-largest-palindrome-with-cutoffs k)
  2.            (let ([m (- (^ 10 k) 1)] [m/10 (^ 10 (- k 1))])
  3.              (define (iter a b largest-palindrome)
  4.                (if (< a m/10) largest-palindrome
  5.                    (if (< b m/10) (iter (- a 1) m largest-palindrome)
  6.                        (let ([n (* a b)])
  7.                          (if (<= n largest-palindrome) (iter (- a 1) m largest-palindrome)
  8.                              (if (palindrome-number? n) (iter (- a 1) m n)
  9.                                  (iter a (- b 1) largest-palindrome)))))))
  10.              (iter m m 0)))

Complexity in memory now is just O(1). Performance complexity by my impression is better than in the previous variant. The outer loop has n - n/10 steps, so it cannot be less than O(n). Assuming that a desired palindrome (left half of it, actually) lies close to n - \sqrt n (which should be proved, strictly speaking), we make no more than n \cdot \sqrt n = n^{3/2} operations until find it, and no more than the same n^{3/2} afterwards.

This is the worst case, however, and I hope that we find some worse-than-ideal palindrome quick enough. Suppose, we can use the estimate n - \sqrt n ab origin, i.e. the inequality f \cdot g < \sqrt n \cdot \sqrt n = n holds, where f = n - a, g = n - b. Then we can calculate an estimate of operations as area under a curve y = n / x:

\int_1^n \frac n x dx = n \cdot \ln n

So, the actual algo performance is between O(n \ln n) and O(n^\frac 3 2).

Problem 4 ver. 2: optimization

November 29th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the largest palindrome made from the product of two 3-digit numbers.

Last time we had a straightforward algo with O(n^2) complexity and at least O(n) memory use. Now let's enhance that. Instead of iterating over multipliers it's reasonable to iterate over palindromes, starting from the largest. I.e. over sequence 999999, 998899, 997799, and so on.

Remark. The largest product of two 3-digit numbers is 999 * 999 = 998001. So, in principle, we could start from the palindrome 997799. But this saves just 2 iterations.

Having a palindrome m, we factorize it and look at all the subsets of the factorization. Assume, we have one subset already. Let's name the product of those factors as p. If this number p has k digits (k = 3 for now) and the number m/p has k digits, than we found the palindrome, which is a product of two k-digit numbers.

In Scheme that will be written as:

  1.         (define (find-largest-palindrome-via-factorization k)
  2.            (define (correct-length? m) (= (length (digits m)) k))
  3.            (define (iter l) (let* ([n (make-number-from-digits (append (reverse l) l))]
  4.                                    [factors (map mul-list (subsets (factorize n minimal-factor-sqrt-complexity)))]
  5.                                    [factors (filter correct-length? factors)]
  6.                                    [factors (filter (lambda (m) (correct-length? (/ n m))) factors)])
  7.                               (if (null? factors) (iter (digits (- (make-number-from-digits l) 1))) n)))
  8.            ;(display (list n '= (car factors) '* (/ n (car factors)))))))
  9.            (iter (one-number-multiple-times 9 k)))

Here I used a few new util functions:

  1.         (define (make-number-from-radix list k)
  2.            (define (iter l f) (if (null? l) 0 (+ (* (car l) f) (iter (cdr l) (* f k)))))
  3.            (iter list 1))
  4.          (define (make-number-from-digits list) (make-number-from-radix list 10))

which make numbers out of their base-k representation.

Complexity now is hard to calculate. The worst case scenario gives quite a bad upper boundary. However, the worst case will never be realized.

Looking at what it gives out (9009, 906609, 99000099, 9966006699, 999000000999, ...), I could guess that the required palindrome is found after roughly \sqrt n iterations. So, in total I hope for less than O(n^2) complexity.

The memory use depends on factorizations - we store one whenever a palindrome is taken and lose it when proceed with the next palindrome.

Problem 3: a note

May 5th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags:

Find the largest prime factor of a composite number.

The problem of integer factorization is one of the most important in the number theory. Last time I implemented a classical algorithm with O(\sqrt{n}) complexity. The author of the Project Euler website suggests a small improvement --- iterating over only odd numbers, but I consider this as too tiny thing to do.

As regards the problem of factorization, I'd rather look in special literature, what are the known approaches (and I'll do that later, let's switch to the next one).

Problem 2 ver. 2, 3, 4: logarithmic complexity

April 17th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the sum of all the even-valued terms in the Fibonacci sequence which do not exceed four million

The last time we had the straightforward O(n) solution: building a sequence, filtering out even values and adding them. We can improve a bit, noticing that actually, every third member of the Fibonacci sequence is even. We don't check then for evennes, but just jump over three components each time. This version 2 (I don't publish it here) should be several times faster, but still is O(n) in performance.

We can also express a member of the Fibonacci sequence via the third and sixth members from behind: F_{n} = 4 F_{n-3} + F_{n-6} and compute those values as the values of a new sequence: E_n = 4 E_{n-1} + E_{n-2}. This version 3 is essentially the same as the previous one and again, I don't publish it here.

The drastic improvement is obtained using the expression \sum_{k=0}^{n} F_{3k} = \frac{F_{3n+2}-1}{2} (I've added it and a proof to the wikipedia article, but they immediately reverted my changes as "unsourced" --- this is pathetic). Now the sum is obtained just computing one Fibonacci member, and this can be done with O(log n).

Indeed, we can compute a Fibonacci member exponentiating the appropriate matrix, and this exponentiation, just like usual one, can be done with O(log n). I prefer this solution over using the golden ratio exponentiation formula (again logarithmic complexity), because only integer-operations are involved. So, this is the version 4 of the solution.

  1.         (define (fibonacci-member-logarithmic n) (matrix-2d-a12 (^-2d fibonacci-matrix n)))
  2.          (define (fibonacci-sum-even n) (/ (- (fibonacci-member-logarithmic (+ n 2)) 1) 2))

I quickly outlined a class for 2D matrices and operations with it:

  1.         (define-record-type matrix-2d (fields a11 a12 a21 a22))
  2.          (define identity-matrix-2d (make-matrix-2d 1 0 0 1))
  3.          (define fibonacci-matrix (make-matrix-2d 1 1 1 0))
  4.          (define (*-2d A B) (let ([a11 (matrix-2d-a11 A)]
  5.                                   [a12 (matrix-2d-a12 A)]
  6.                                   [a21 (matrix-2d-a21 A)]
  7.                                   [a22 (matrix-2d-a22 A)]
  8.                                   [b11 (matrix-2d-a11 B)]
  9.                                   [b12 (matrix-2d-a12 B)]
  10.                                   [b21 (matrix-2d-a21 B)]
  11.                                   [b22 (matrix-2d-a22 B)])
  12.                               (make-matrix-2d (+ (* a11 b11) (* a12 b21)) (+ (* a11 b12) (* a12 b22))
  13.                                               (+ (* a21 b11) (* a22 b21)) (+ (* a21 b12) (* a22 b22)))))
  14.          (define (^-2d-linear A n) (apply-n-times identity-matrix-2d n (lambda (x) (*-2d x A))))
  15.          (define (^-2d-logarithmic A n) (if (= n 0) identity-matrix-2d
  16.                                             (if (odd? n) (*-2d A (^-2d-logarithmic A (- n 1)))
  17.                                                 (let ([B (^-2d-logarithmic A (div n 2))]) (*-2d B B)))))
  18.          (define ^-2d ^-2d-logarithmic)

The solution is O(1) in memory and O(log n) in performance - of course, where n denotes the index of a number in the Fibonacci sequence. And we have been questioned about the cutset, where members of a sequence are less than certain number. Then an additional function (closest-fibonacci-index) comes in handy (see the wiki for explanation):

  1.         (define golden-ratio (/ (+ 1 (sqrt 5)) 2))
  2.          (define (closest-fibonacci-index f) (round (log (* f (sqrt 5)) golden-ratio)))
  3. (define (solution-2-optimized-3 n) (fibonacci-sum-even (closest-fibonacci-index n)))

The final touch is asking ourselves about complexity of the (log) function. Well, it can be computed fast enough not to spoil complexity of the algo's main part.

Problem 1 ver. 3: optimization

April 5th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the sum of all the multiples of 3 or 5 below 1000.

Let us generalize again to a finite set of factors.

There is a formula for the power of finite sets

|A \bigcup B| = |A| + |B| - |A \bigcap B|

which can be generalized to a finite number of finite sets

p(\bigcup\limits_i^n A_i) = \sum\limits_{i_1} p(A_{i_1}) - \sum\limits_{i_1,i_2} p(A_{i_1} \bigcap A_{i_2}) + \sum\limits_{i_1,i_2,i_3} p(A_{i_1} \bigcap A_{i_2} \bigcap A_{i_3}) - ... + (-1)^{n-1} p(\bigcap\limits_i^n A_i)

or in a somewhat less understandable, but concise notation

p(\bigcup\limits_i^n A_i) = \sum\limits_{\alpha \in 2^{\mathbb{N}_n}} (-1)^{|\alpha|-1} p(\bigcap\limits_{j \in \alpha} A_j)

Here p(...) is a measure (i.e. it commutes with the union sign) and can be replaced with |...| --- power of a set sign or, if we are in the natural numbers space, with the sum of elements sign, as in our case. \alpha is not a multiindex, but a subset of the natural numbers cut from 1 to n.

Now by \bigcup_{i=1}^n A_i we denote the set of all the multiples of factors f_i, less than certain number N, where i varies from 1 to n (each A_i is respectively the set of multiples of a factor f_i). We use the above-mentioned formula to compute the measure of the union \bigcup_{i=1}^n A_i via measures of all A_i and measures of all finite intersections of them.

Suppose, we have a number a, prime or not, and the set of all it's multiples A (they include only numbers less than N). Power of this set is of course N \div a (div operation) and the sum of its members can be calculated by the well-known formula for the sum of an arithmetic progression.

As regards all the intersections, it is understandable that we ought to calculate the least common multiple (LCM) of taken factors, and the set-intersection of their multiples will be just a set of its multiples. However, the current version of the solution assumes that we take primes as factors, then the LCM of them is just their product. When I calculate proper LCM in Problem 5 (up to now there is a bruteforce version), I will switch the temp version to it.

Let's see the solution. New util functions:

  1. (define (mul-list list) (fold-left * 1 list))
  2. (define (^ base power) (expt base power))
  3. (define (sum-arithmetic-progression first step n) (/ (* n (+ (* 2 first) (* step (- n 1)))) 2))

The function that calculates subsets of a set:

  1. (define (subsets set)
  2.   (define (recursion set rest) (if (null? set) (list rest)
  3.                                    (let ([head (car set)] [tail (cdr set)])
  4.                                      (append (recursion tail rest) (recursion tail (cons head rest))))))
  5.   (recursion set '()))

Important thing about this function is that it returns the empty set as the first element and the full set as the last element of a result list, all other subsets are in between. The number of subsets of a finite subset is just 2^n, so the complexity is O(2^n) --- it would be better visible with an imperative-iterative version of this function (I'm not posting it here). As regards memory, the function generates all the subsets as lists which in whole contain \sum_{k=1}^n k C^n_k = n 2^{n-1} elements (strange, this neat formula isn't on Wikipedia yet, I should add it there), that is the memory load is O(n 2^n). This is a not-so-good idea to load everything into memory, as we can rewrite this function (and the function that is down here in the post) iteratively with O(n) memory complexity --- taking advantage of combinadics, but for now I am satisfied enough with this version.

Using the formula above the solution now as simple as

  1. (define (sum-multiples-less-than n divisors)
  2.   (define (sum-of-one factor) (sum-arithmetic-progression factor factor (div n factor)))
  3.   (define (lcm-temp factors) (if (null? factors) 0 (mul-list factors)))
  4.   (define (measure subset) (* (^ (- 1) (+ (length subset) 1)) (sum-of-one (lcm-temp subset))))
  5.   (sum-list (map measure (cdr (subsets divisors)))))

With that (cdr) I cut off the empty subset, whose measure is zero (otherwise the (sum-of-one) function has to be a bit more complex).

Let's be careful with notation: n here is actually not the same n, as in the (subsets) function, but the number N up there, the maximum of our multiples-sets. The performance complexity depends on k and N, but we are interested only in complexity, depending on N. Let's assume that k is small comparing to N, which should be the usual case. Then the complexity is roughly speaking O(1), doesn't depend on N, as we wanted (I remind that in the previous version we had O(N) complexity).

The final touches are the regression tests:

  1. (assert (=
  2.          (sum-list (multiples-less-than-bruteforce 10 '(3 5)))
  3.          (sum-multiples-less-than 10 '(3 5))))
  4. (assert (=
  5.          (sum-list (multiples-less-than-bruteforce 1000 '(3 5)))
  6.          (sum-multiples-less-than 1000 '(3 5))))
  7. (assert (=
  8.          (sum-list (multiples-less-than-bruteforce 10000 '(3 5 7 19)))
  9.          (sum-multiples-less-than 10000 '(3 5 7 19))))
  10. ;(assert (=
  11. ;        (sum-list (multiples-less-than-bruteforce 1000 '(3 5 15)))
  12. ;       (sum-multiples-less-than 1000 '(3 5 15))))

The last commented one breaks, of course, as 15 is not prime - the LCM algo should be updated still.

Problem 7 ver. 1 and 2: bruteforce and optimization

March 8th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the 10001st prime.

The problem of finding primes is the most famous computational task in the number theory.

At first, let's update the (generate-list-iteratively) function. It now includes i - the parameter, which denotes number of eligible elements up to this moment. We use it in the stopping condition.

  1.         (define (generate-list-iteratively seed filter map step stop)
  2.            (define (iterate current i) (if (stop current i) '()
  3.                                            (let* ([ok (filter current)] [next (if ok (+ i 1) i)] [rest (iterate (step current) next)])
  4.                                              (if ok (cons (map current) rest) rest))))
  5.            (iterate seed 0))

Dependant functions are rewritten also. I refactored the factorization functions and added primality tests (usual straightforward ones):

  1.         (define (minimal-factor-bruteforce n)
  2.            (define (iterate n x) (if (> x n) n (if (divides? n x) x (iterate n (+ x 1)))))
  3.            (iterate n 2))
  4.          (define (minimal-factor-sqrt-complexity n)
  5.            (define (iterate n x) (if (> x (sqrt n)) n (if (divides? n x) x (iterate n (+ x 1)))))
  6.            (iterate n 2))
  7.          (define (factorize n minimal-factor) (let ([p (minimal-factor n)]) (if (= p n) (list p) (cons p (factorize (/ n p) minimal-factor)))))
  8.          (define (prime?-bruteforce n) (= (minimal-factor-bruteforce n) n))
  9.          (define (prime?-sqrt-complexity n) (= (minimal-factor-sqrt-complexity n) n))
  10.          (define (primes-list n prime?) (generate-list-iteratively 2 prime? (lambda (x) x) (lambda (x) (+ x 1)) (lambda (x i) (= i n))))

Now, having an extra function that returns the last element in a list, we can easily solve the 7-th problem:

  1.         (define (last list) (if (null? (cdr list)) (car list) (last (cdr list))))
  2. (define (solution-7-bruteforce n) (last (primes-list n prime?-bruteforce)))
  3. (define (solution-7-optimized-1 n) (last (primes-list n prime?-sqrt-complexity)))

The complexity in memory is O(n), as we store n primes. It could be O(1), of course, but I prefer this way for now. The performance complexity of the algorithm is hard to determine. Probably, I need to overlook some literature for that.

Problem 6 ver. 1: brute-force

March 6th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

What is the difference between the sum of the squares and the square of the sums?

No problems with brute-force here.

  1.         (define (square n) (* n n))
  2. (define (solution-6-bruteforce n) (let ([ns (numbers 1 n)]) (- (square (sum-list ns)) (sum-list (map square ns)))))

The number of operations (additions and multiplications) here is 3*n + 2. The complexity is O(n), and the algo is O(n) in memory as well (of course, can be done with O(1) in memory).

Problem 5 ver. 1: brute-force

March 6th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

What is the smallest number divisible by each of the numbers 1 to 20?

This is also quite known problem --- finding a least common multiple (LCM).

As usual, let's start with the brute-force solution.

  1.         (define (least-common-multiple-bruteforce divisors)
  2.            (define (iterate x) (if (all-divide? x divisors) x (iterate (+ x 1))))
  3.            (iterate (max-list divisors)))
  4. (define (solution-5-bruteforce n) (least-common-multiple-bruteforce (numbers 2 n)))

This is the most inefficient way we can invent for the task of finding LCM. The complexity in the worst case is something like O(p^n), when n divisors out there are prime numbers, each is p at the average (but different, of course, then their LCM is just their product). More strictly, the number of divisions is up to n*p^n in the worst case (would be that if not shorcutted logical operations in (all-divide?) function).

Problem 4 ver. 1: brute-force

March 5th, 2009 by Ivan Lakhturov | 0 Category: Programming | Tags: |

Find the largest palindrome made from the product of two 3-digit numbers.

An additional function that filters out empty lists from a list of lists:

  1.         (define (filter-empty-lists lists) (filter (lambda (list) (not (null? list))) lists))

The function that represents a number in arbitrary number system and the function that returns 10-base digits for a number (starting from the rightmost digit):

  1.         (define (represent-in-radix n k) (if (= n 0) '() (cons (mod n k) (represent-in-radix (div n k) k))))
  2.          (define (digits n) (represent-in-radix n 10))

(digits) function could use standard (number->string), but this generalized solution I like more. Now a solution to the problem looks as easy as:

  1.         (define (palindrome-number? n) (let ([d (digits n)]) (equal? d (reverse d))))
  2.          (define (find-palindromes-among-products n) (map (lambda (x) (map (lambda (y) (* x y)) (numbers-filtered x n (lambda (y) (palindrome-number? (* x y)))))) (numbers 1 n)))
  3.  
  4. (define (solution-4-bruteforce n) (max-list (map max-list (filter-empty-lists (find-palindromes-among-products n)))))

Notice, for comparing lists we use (equal?), not (eq?) and not (eqv?) - they would return just #f. However, we could use (eq?) instead of (null?) in (filter-empty-lists) to compare a list to the empty one. As regards number of operations, we have n^2 / 2 multiplications, as many (palindrome-number?) calls that create digits lists (up to 6 divmod per each) and compare them (up to 6 comparisons per each). So, the complexity is O(n^2), and memory use is proportional to the palindrome numbers density in the n by n matrix (half of which we actually iterate over to find an answer).