Eli Bendersky's websitehttp://eli.thegreenplace.net/2009-07-28T14:58:35-07:00Euler 68 - solving problems the hard way, but having fun along the way2009-07-28T14:58:35-07:00Eli Benderskytag:eli.thegreenplace.net,2009-07-28:2009/07/28/euler-68-solving-problems-the-hard-way-but-having-fun-along-the-way
Project Euler <a href="http://projecteuler.net/index.php?section=problems&id=68">problem 68</a> is your typical puzzle - you have to fill in some numbers into a pattern according to certain rules (constraints). Kind-of like Sudoku, just simpler.
The approaches people have used to solve it are quite varied:
<ul>
<li>By hand, using pencil and paper. It isn't a very hard puzzle, after all.</li>
<li>Brute forcing the 10! permutations of 10 numbers, checking which permutation satisfies all the constraints and has the largest 16-digit string representation.</li>
<li>Smarter brute forcing with some pruning of bad paths - this comes closer to AI approaches.</li>
<li>Some people recognized that this can be easily solved by a constraint-satisfaction engine, found such an engine and used it to solve the problem (there's still a challenge here, and that's representing the problem in the language of the constraint satisfier).</li>
<li>The most brilliant solution I saw was using SQL! The constraints were coded as a SQL <code>select</code> statement - not the most efficient solution, but an original one!</li>
</ul>
Now, as I tend to do in many cases, I took the long and hard way here. I coded my own constraint satisfaction engine in Python - complete with constraint propagation, search pruning and some generic heuristics. For sample problems I represented map coloring, Sudoku, magic squares and N-queens problems. The solver works quite fine for all these problems, and when I represented the 5-gon problem for it, it was easy to find the solution quickly.
It was real fun coding the CSP solver (kinda like the time when I coded a simple
<a href="http://eli.thegreenplace.net/2007/04/08/sudoku-as-a-sat-problem/">SAT solver to solve Sudokus</a>) - it was an educational experience. True, solving problem 68 took 6-7 hours instead of 20 minutes this way, but it was definitely worth it!
The code for the solver is available <a href="https://github.com/eliben/code-for-blog/tree/master/2009/csp_for_euler68">here</a>. Drop me an email if you have any questions about it.
Solved 100th problem on Project Euler!2009-06-27T07:15:51-07:00Eli Benderskytag:eli.thegreenplace.net,2009-06-27:2009/06/27/solved-100th-problem-on-project-euler
I've just solved my 100th problem on <a href="http://projecteuler.net/">Project Euler</a> and got this nice message:
<blockquote>Excellent work, eliben! By solving your 100th problem you have earned a place among the top 2.53% of members and have advanced to level 3.
</blockquote>
So I'm <a href="http://projecteuler.net/index.php?section=profile&profile=eliben">level 3</a> now!
<img src="http://eli.thegreenplace.net/images/2009/06/octahedron.gif" title="octahedron" width="100" height="104" class="alignnone size-full wp-image-1751" />
P.S.: Problem 125 is very easy
Project Euler problem 83 - how creeps can help2009-06-19T17:10:47-07:00Eli Benderskytag:eli.thegreenplace.net,2009-06-19:2009/06/19/project-euler-problem-83-how-creeps-can-help
Problems 18, 67, 81, 82 and 83 in Project Euler are quite similar, but increase in level of difficulty.
For all of these except 83 I've used a specialized dynamic-programming solution that worked quite well. Being recursive it was a tiny bit slower than the iterative approaches others have used, but I believe recursive solutions are more elegant.
Anyway, project 83 proved too hard for my approach, because it introduces real cycles into the paths. So, this time I had to arm myself with a real path-finding algorithm. Luckily, I've coded a pretty generic implementation of the A* algorithm quite recently for my <a href="http://eli.thegreenplace.net/2009/01/09/writing-a-game-in-python-with-pygame-part-iii/">Creeps PyGame demo</a>.
A*, however, requires a heuristic to do its magic, and for problem 83 there's no real heuristic. However, since a constant heuristic is acceptable and effectively "dumbs down" A* to the original Dijkstra algorithm (the heuristic of A* must be non-decreasing, which a constant satisfies), I was able to make use of the code from the Creeps game to find a solution in 0.3 seconds, with only 30 lines of code.
Project Euler problem 66 and continued fractions2009-06-19T14:49:07-07:00Eli Benderskytag:eli.thegreenplace.net,2009-06-19:2009/06/19/project-euler-problem-66-and-continued-fractions
<p><a class="reference external" href="http://projecteuler.net/index.php?section=problems&id=66">Problem 66</a> is one of those problems that make Project Euler lots of fun. It doesn't have a brute-force solution, and to solve it one actually has to implement a non-trivial mathematical algorithm and get exposed to several interesting techniques.</p>
<p>I will not post the solution or the full code for the problem here, just a couple of hints.</p>
<p>After a very short bout of Googling, you'll discover that the Diophantine equation:</p>
<p><img src="http://eli.thegreenplace.net/images/math/cdc11e760e8b319f652e19c6daf547cbe9d0b0f9.gif" /></p>
<p>Is quite famous and is called <a class="reference external" href="http://en.wikipedia.org/wiki/Pell%27s_equation">Pell's equation</a>. From here, further web searches and Wikipedia-reading will bring you to at least two methods for finding the <em>fundamental solution</em>, which is the pair of <tt class="docutils literal"><span class="pre">x</span></tt> and <tt class="docutils literal"><span class="pre">y</span></tt> with minimal <tt class="docutils literal"><span class="pre">x</span></tt> solving it.</p>
<p>One of the methods involves computing the continued-fraction representation of the square root of <tt class="docutils literal"><span class="pre">D</span></tt>. <a class="reference external" href="http://www.mcs.surrey.ac.uk/Personal/R.Knott/Fibonacci/cfINTRO.html">This page</a> is a must read on this topic, and will help you with other Euler problems as well.</p>
<p>I want to post here a code snippet that implements the continued-fraction computation described in that link. Its steps follow the <em>Algebraic algoritm</em> given there:</p>
<div class="highlight"><pre><span style="color: #00007f; font-weight: bold">def</span> <span style="color: #00007f">CF_of_sqrt</span>(n):
<span style="color: #7f007f">""" Compute the continued fraction representation of the</span>
<span style="color: #7f007f"> square root of N.</span>
<span style="color: #7f007f"> The first element in the returned array is the whole</span>
<span style="color: #7f007f"> part of the fraction. The others are the denominators</span>
<span style="color: #7f007f"> up to (and not including) the point where it starts</span>
<span style="color: #7f007f"> repeating.</span>
<span style="color: #7f007f"> Uses the algorithm explained here:</span>
<span style="color: #7f007f"> http://www.mcs.surrey.ac.uk/Personal/R.Knott/Fibonacci/cfINTRO.html</span>
<span style="color: #7f007f"> In the section named: "Methods of finding continued</span>
<span style="color: #7f007f"> fractions for square roots"</span>
<span style="color: #7f007f"> """</span>
<span style="color: #00007f; font-weight: bold">if</span> is_square(n):
<span style="color: #00007f; font-weight: bold">return</span> [<span style="color: #00007f">int</span>(math.sqrt(n))]
ans = []
step1_num = <span style="color: #007f7f">0</span>
step1_denom = <span style="color: #007f7f">1</span>
<span style="color: #00007f; font-weight: bold">while</span> <span style="color: #00007f">True</span>:
nextn = <span style="color: #00007f">int</span>((math.floor(math.sqrt(n)) + step1_num) / step1_denom)
ans.append(<span style="color: #00007f">int</span>(nextn))
step2_num = step1_denom
step2_denom = step1_num - step1_denom * nextn
step3_denom = (n - step2_denom ** <span style="color: #007f7f">2</span>) / step2_num
step3_num = -step2_denom
<span style="color: #00007f; font-weight: bold">if</span> step3_denom == <span style="color: #007f7f">1</span>:
ans.append(ans[<span style="color: #007f7f">0</span>] * <span style="color: #007f7f">2</span>)
<span style="color: #00007f; font-weight: bold">break</span>
step1_num, step1_denom = step3_num, step3_denom
<span style="color: #00007f; font-weight: bold">return</span> ans
</pre></div>
<p>As I said, this still isn't enough to solve the problem, but with this code in hand, the solution isn't too far. Read some more about Pell's equation and you'll discover how to use this code to reach a solution.</p>
<p>It took my program ~30 milliseconds to find an answer to the problem, by the way. It took less than a second to solve a 10-times larger problem (for D <= 10000), so I believe it to be a pretty good implementation.</p>
Posting Project Euler solutions in my blog2009-03-02T18:49:09-08:00Eli Benderskytag:eli.thegreenplace.net,2009-03-02:2009/03/02/posting-project-euler-solutions-in-my-blog
Some may find it inappropriate that I post solutions to Project Euler in this blog. I don't share this sentiment, however. Here's an excerpt from the PE FAQ:
<blockquote>
I solved it by using a search engine, does that matter?
That depends on your motivation for solving the problems. It probably means that you've missed out on some beautiful and hidden mathematics.
</blockquote>
If anyone races through PE using solutions of others, what has he gained? Will others feel they've been robbed? Isn't this, at the core, just an issue of personal integrity?
Anyways, if anyone's determined to find solutions to PE online, it's easy. This <a href="http://www.haskell.org/haskellwiki/Project_euler">Haskell Wiki</a>, for instance, holds code for the first 200 solutions. And other resources can be easily found by Googling.
I'm posting hints for some selected problems because I feel like sharing these insights with the readers of my blog, and this is also a good way to compare solutions with friends. It's the same motivation I've used for posting the solutions to almost all <a href="http://eli.thegreenplace.net/tag/sicp">SICP exercises online</a>. For some reason no one objected to that, although solved SICP exercises can lead to students cheating in homework, while Project Euler is just a game.
So take it easy, and if you don't want to spoil the solving experience for yourself, just don't look at these posts! I've conveniently hidden all the real hints behind a <em>Read More</em> link, so it won't be accidentally caught in your eyesight.
Project Euler problem 1042009-03-01T22:01:16-08:00Eli Benderskytag:eli.thegreenplace.net,2009-03-01:2009/03/01/project-euler-problem-104
Problem <a href="http://projecteuler.net/index.php?section=problems&id=104">104</a> was probably the most difficult I've tackled so far. At first I've attempted brute-force, but after several long minutes of runtime realized it just won't work. And well I did, because it turns out the number I'm looking for is over 68000 digits long. Such numbers are quite consuming to process.
So I had to resort to some numerical tricks to reach the solution.
<!--more-->
<strong>Observation #1</strong>
To find numbers where the low 9 digits are pandigital, we don't really have to consider the whole numbers, but only their lowest 9 digits. The higher digits don't affect the low ones in addition, so once can generate the 9 lowest digits of the Fibonacci numbers easily.
These are trivially checked for being pandigital, so it's simple to generate "candidates" for the answer. This way I quickly find out the <img src="http://eli.thegreenplace.net/images/math/042dc4512fa3d391c5170cf3aa61e6a638f84342.gif" />s for which <img src="http://eli.thegreenplace.net/images/math/98b3cea2822dd71a93430170fc8c923f192127ff.gif" /> has pandigital low digits.
<strong>Observation #2</strong>
Once we have the indices of candidates, we can directly find the numbers by using the following cool formula for Fibonacci numbers:
<p><img src="http://eli.thegreenplace.net/images/math/237107811dea34552f65ba09642e46238115006b.gif" class="align-center" /></p>
<p><img src="http://eli.thegreenplace.net/images/math/1f90f4c874c14b9383709bce565865c4995b50a3.gif" class="align-center" /></p>
As you can see, the runtime of such a computation is logarithmic, and with some simple caching it's a very efficient algorithm for generating the <img src="http://eli.thegreenplace.net/images/math/042dc4512fa3d391c5170cf3aa61e6a638f84342.gif" />th Fibonacci number.
So I could just test my candidates for upper-digit pandigital this way, and the problem was solved in about a minute of runtime.
<strong>Observation #3</strong>
I'm pretty sure this can be done even more efficiently, using the golden ratio:
<p><img src="http://eli.thegreenplace.net/images/math/8e47b854a9554dc5e2fc7134705db1ffd26c3cd0.gif" class="align-center" /></p>
Since we only need the upper 9 digits, using the golden ratio with sufficient accuracy, the Fibonacci numbers can be approximated enough to see the exact upper 9 digits. I'm sure this method is much faster than the one I've used.
Project Euler problem 692009-02-28T17:55:54-08:00Eli Benderskytag:eli.thegreenplace.net,2009-02-28:2009/02/28/project-euler-problem-69
Problem <a href="http://projecteuler.net/index.php?section=problems&id=69">69</a> is a really nice one. I looked at it, and immediately it seemed that brute-forcing is not required here. Yeah, Ron, I know it took you 35.671 ms to brute-force it :-), but here's a nicer mathematical solution without programming at all:
<!--more-->
A <a href="http://en.wikipedia.org/wiki/Euler%27s_totient_function">formula to compute</a> <img src="http://eli.thegreenplace.net/images/math/0eadb84a094d01982d327d9c13c0cd9fc5385806.gif" /> is:
<p><img src="http://eli.thegreenplace.net/images/math/01d04d657df3c939d1d3ad4cabf9e63b7d9104f4.gif" class="align-center" /></p>
Where p are the distinct primes dividing n. To minimize <img src="http://eli.thegreenplace.net/images/math/01adbadeca453d142cab4fa2ab3b457e76f56bce.gif" /> we see that n should have as many prime factors as possible, and the smaller they are, the better.
Intuitively, if n has a prime factor p, then every p-th number upto n is not co-prime with n. Therefore, it would be great if every 2nd, 3rd, 5th etc. number were not co-prime with n, this would leave very few numbers that are co-prime with it, thus minimizing <img src="http://eli.thegreenplace.net/images/math/01adbadeca453d142cab4fa2ab3b457e76f56bce.gif" />.
So, to solve it, find a number that's a multiple of as many small primes as possible and is still less than 1,000,000
Project Euler problem 432009-02-28T11:15:14-08:00Eli Benderskytag:eli.thegreenplace.net,2009-02-28:2009/02/28/project-euler-problem-43
Problem <a href="http://projecteuler.net/index.php?section=problems&id=43">43</a> is the 51st Project Euler problem I've solved. To do it, I've used a couple of tricks that reduced the runtime considerably from a full brute-force.
A full brute-force for such a problem would be going over all 10-digit numbers, detecting pandigital ones and checking the requested property. Since there are 10 billion 10-digit numbers, this would take a long time.
Here are some ideas of how to reduce the run-time:
<!--more-->
First of all, there are only 3,628,800 (10!) 0 to 9 pandigital numbers, so there's no need to examine all 10 billion 10-digit numbers.
Second, the special property we're looking for in the numbers can help us considerably reduce the amount of candidates to test.
What I did is consider only numbers whose last 3 digits divide by 17. There are only 58 such numbers (1 to 58, as 17 * 59 > 1000). For each, we should only check the permutations of the 7 remaining digits. Since there are 5040 permutations of 7 digits, we have to check only 58 * 5040 = 292320 candidates for the property.
This, of course, can be reduced further by considering the division by 13 as well, and so on, so I'm sure the problem can even be solved using a pen, some paper and a calculator in not too much time.
Project Euler problem 262009-02-25T20:41:47-08:00Eli Benderskytag:eli.thegreenplace.net,2009-02-25:2009/02/25/project-euler-problem-26
<p>Problem <a class="reference external" href="http://projecteuler.net/index.php?section=forum&id=26">26</a> is another problem from Project Euler with a very cool solution. I suppose it can be solved by sheer brute-force, but it will take long and won't be elegant.</p>
<p>After some manual experimentation with long division on paper and then with a calculator it becomes clear that some things are very systematic here.</p>
<!--more-->
<p>There are 3 kinds of rational numbers:</p>
<ul class="simple">
<li>Expansion is finite (for instance 1/4 = 0.25): those are numbers of the form <img src="http://eli.thegreenplace.net/images/math/405ca93725cc98361fd219b0dbd9c5f8fd8818af.gif" /></li>
<li>Expansion is repetitive, from the start (like 1/7 = 0.(142857)). This is for numbers co-prime to 10.</li>
<li>Expansion is repetitive, after some initial non-repetitive section. This is for numbers that have both factors co-prime to 10 and are divisible by 2 or 5. (for example 1/14 = 0.0(714285))</li>
</ul>
<p>To find the longest cycle it's sufficient to look just at the primes - because other numbers will just have the same cycles after some initial non-repetitive section.</p>
<p>But how do you find the length of the cycle efficiently? This is where number theory comes in! <a class="reference external" href="http://mathworld.wolfram.com/DecimalExpansion.html">This page</a> contains a lot of very interesting information about this problem.</p>
<p>Eventually, it all boils down to the <a class="reference external" href="http://mathworld.wolfram.com/MultiplicativeOrder.html">multiplicative order</a> of the denominator.</p>
<p>We have to solve the <a class="reference external" href="http://en.wikipedia.org/wiki/Discrete_logarithm">discrete logarithm</a>:</p>
<p>
<p><img src="http://eli.thegreenplace.net/images/math/77eb38a3d9bca894a573807ea9b647748fe6a51d.gif" class="align-center" /></p>
</p>
<p><tt class="docutils literal"><span class="pre">k</span></tt> is the cycle length for <tt class="docutils literal"><span class="pre">n</span></tt>, for numbers co-prime to 10. The discrete logarithm is a difficult problem, but fortunately the numbers we're dealing here are small.</p>
<p>It took my Python script about one-tenth of a second to find the answer using this method.</p>
Project Euler problem 122009-02-20T13:15:00-08:00Eli Benderskytag:eli.thegreenplace.net,2009-02-20:2009/02/20/project-euler-problem-012
[latexpage]
I've got hooked to <a href="http://projecteuler.net/">Project Euler</a> this week, after hearing good stuff about it for a long time. I've already solved the first 16 problems, and it has been lots of fun so far.
Most of the problems I've encountered are trivial, and can be either solved by a simple brute-force approach or by using a known mathematical formula. Besides, Python's built-in and efficient arbitrary-precision arithmetic makes many problems very trivial (such as <a href="http://projecteuler.net/index.php?section=problems&id=16">16</a> - the sum of digits in the number <img src="http://eli.thegreenplace.net/images/math/a84aa60b1755994d739ef298e63385180293ef02.gif" />).
The first problem that gave me some hard time was 12 - I just couldn't brute-force it. Eventually, a couple of insights led me to the solution, which was very satisfying.
Below are some tips for solving it, though I don't provide the full solution.
<!--more-->
The most important insight is that you don't have to enumerate all the numbers up to <img src="http://eli.thegreenplace.net/images/math/4d938c396f47c60dfde895b32aa18df3c874741c.gif" /> and check if they divide it, counting those that do.
A faster method is to factorize N, and use the <a href="http://en.wikipedia.org/wiki/Divisor_function">divisor function</a> to compute the amount of divisors.
If the factorization of N is:
<p><img src="http://eli.thegreenplace.net/images/math/fd7787f1820f879a010d4bf63216479afdd5afd2.gif" class="align-center" /></p>
Where <img src="http://eli.thegreenplace.net/images/math/43f895a990ded7c405204550e21d23e2f877f6a8.gif" /> are the prime factors and <img src="http://eli.thegreenplace.net/images/math/6788bc4a044ce6b6f0ef426ad92596de04cb3f8f.gif" /> are their exponents, then the total amount of divisors of N is:
<p><img src="http://eli.thegreenplace.net/images/math/4817daba8e67f0ad76305b271007ca9f9d7f7bf3.gif" class="align-center" /></p>
Even with the crappy factorizing routine I used, this produces the amount of divisors for a given number <em>much</em> faster than the naive method.
The second key insight (without which it would also take too long to solve this problem) is that since we're looking for the <em>smallest</em> number with 500 divisors, it will definitely have small prime factors. So I just threw away anything that didn't divide by 2, 3, 5 and so on. After a few experimentations, I had an algorithm that finds the desired number in about 4 seconds.