diff options
| author | James Smith <baggy@baggy.me.uk> | 2021-07-27 07:21:46 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-07-27 07:21:46 +0100 |
| commit | 77314f65a59cf5bbc286fb0e3a0b13f46d852f6e (patch) | |
| tree | bce63d5914b665545ede7a12369d89159657c640 | |
| parent | 4f8ee21584432dc56c9df610b40b5dc921135f54 (diff) | |
| download | perlweeklychallenge-club-77314f65a59cf5bbc286fb0e3a0b13f46d852f6e.tar.gz perlweeklychallenge-club-77314f65a59cf5bbc286fb0e3a0b13f46d852f6e.tar.bz2 perlweeklychallenge-club-77314f65a59cf5bbc286fb0e3a0b13f46d852f6e.zip | |
Update README.md
| -rw-r--r-- | challenge-123/james-smith/README.md | 62 |
1 files changed, 37 insertions, 25 deletions
diff --git a/challenge-123/james-smith/README.md b/challenge-123/james-smith/README.md index 3d6e446896..85b1e201e4 100644 --- a/challenge-123/james-smith/README.md +++ b/challenge-123/james-smith/README.md @@ -51,45 +51,57 @@ We cache the values internally in the function - in the `state` variable `@uglie ### Optimization -We can speed this up by short-cutting the inner loop - by remembering which was the first ugly failed the check `$_ * $l <= $uglies[-1]`. +We can speed this up by short-cutting the inner loop. + * All uglies are either a multiple of 2, 3 or 5 times another ugly (with the exception of 1). + * We keep track of the next ugly that is a multiple of 2, 3, 5 etc - we call these `$v2`, `$v3` and `$v5` respectively. These are `2*$uglies[$i2]`, `3*$uglies[$i3]`, `5*$uglies[$i5]`. + * Initially the values of `$l2`, `$l3`, `$l5`, `$v2`, `$v3`, `$v5` are `0`, `0`, `0`, `2`, `3`, `5`, and the list `@uglies` is initialized with the value `(1)`. + * Every time we need a new ugly, we find it as the lowest value of `$v2`, `$v3`, `$v5`. We then `push` it onto `@uglies`. + * Now we need to update `$v2`, `$v3` and `$v5` if they are equal to this value. We do this by incrementing the index `$i2`, `$i3` and/or `$i5` and then setting + `$v? = ?*$uglies[$i?]`... This will often update 2 or even all 3 of the values... + +Additionally we speed up the code by keeping a cache of ugly values we have found, and if we are asked for one we return that value from the cache, if not as we have +kept the state of the loop we just continue from where we left off with the values of `$l2`, `$l3`, `$l5`, `$v2`, `$v3`, `$v5` which are also held in the state +variable. + +This gives us the following optimized perl code. ```perl sub nth_ugly_opt { my $n = shift; - state ($l2,$l3,$l5,$v2,$v3,$v5,@uglies)=(0,0,0,2,3,5,1); + state $l2 = 0; state $l3 = 0; state $l5 = 0; + state $v2 = 2; state $v3 = 3; state $v5 = 5; + state @uglies = (1); return $uglies[$n-1] if $n <= @uglies; while( @uglies < $n ) { - push @uglies, my $next = ($v2 < $v3 && $v2 < $v5) ? $v2 : ($v3 < $v5) ? $v3 : $v5; + push @uglies, my $next = $v2<$v3 && $v2<$v5 ? $v2 : $v3<$v5 ? $v3 : $v5; $v2 = $uglies[++$l2]*2 if $v2 == $next; $v3 = $uglies[++$l3]*3 if $v3 == $next; $v5 = $uglies[++$l5]*5 if $v5 == $next; } return $uglies[-1]; } - ``` -The extra overhead causes the optimized method to be slower than the simple method for low values of `$n` - but as `$n` goes up it saves many loop evaluations. Flip happens when `$n` is around `18` or `19`. By the time we get to `$n` as 1000 the optimized version is around 50 times faster than the naive one. For `$n = 10_0000` one of the largest values for which the ugly is less than 2^64 (and so all calculations are integer based) in around 600 times faster. - -Below shows these comparisons - against a simple scan algorithm (with caching of uglies). We can see that for values up to around 20-30 the scan method (because of it's simplicity is fastest) and the uglies are relatively dense. But soon the advantage of our message is seen - by the time `$n` reaches 500 (Ugly is 937,500) to optimized -method is around 2000x faster than the scan method. - -| n | Ugly(n) | scan /s | simple /s | opt /s | opt vs sim % | sim vs scn % | opt vs scn % | -| -----: | -------------------------: | ------: | --------: | --------: | -----------: | -----------: | -----------: | -| 1 | 1 | 938,492 | 3,005,191 | 1,799,451 | -40 | 220 | 92 | -| 2 | 2 | 536,552 | 816,345 | 1,089,848 | 34 | 52 | 103 | -| 5 | 5 | 234,116 | 238,716 | 455,051 | 91 | 2 | 94 | -| 10 | 12 | 98,061 | 77,865 | 250,411 | 222 | -21 | 155 | -| 20 | 36 | 32,105 | 21,225 | 130,707 | 516 | -34 | 307 | -| 50 | 243 | 4,289 | 5,504 | 43,065 | 682 | 28 | 904 | -| 100 | 1,536 | 724 | 1,203 | 24,768 | 1,959 | 66 | 3,321 | -| 200 | 16,200 | 63.50 | 272 | 12,470 | 4,485 | 328 | 19,538 | -| 500 | 937,500 | 0.57 | 48 | 4,639 | 9,565 | 8,306 | 812,334 | -| 1,000 | 51,200,000 | - | 10.60 | 2,503 | 23,513 | - | - | -| 2,000 | 8,062,156,800 | - | 2.75 | 1,187 | 43,064 | - | - | -| 5,000 | 50,837,316,566,580 | - | 0.41 | 375 | 91,812 | - | - | -| 10,000 | 288,325,195,312,500,000 | - | 0.08 | 230 | 273,710 | - | - | -| 13,282 | 18,432,000,000,000,000,000 | - | 0.05 | 148 | 302,757 | - | - | +Below is the performance of the methods. Note these were tested without using `state` variables, as the caching nature of +state variables prevents benchmarking (values are obtained directly from the cache) - although if you were using this in a +real world situation that would be an advantage! + +| n | Ugly(n) | scan /s | simple /s | opt /s | opt vs sim % | sim vs scn % | opt vs scn % | +| -----: | -------------------------: | --------: | ------------: | ------------: | -----------: | -----------: | -----------: | +| 1 | 1 | *938,492* | **3,005,191** | 1,799,451 | -40 | 220 | 92 | +| 2 | 2 | *536,552* | 816,345 | **1,089,848** | 34 | 52 | 103 | +| 5 | 5 | *234,116* | 238,716 | **455,051** | 91 | 2 | 94 | +| 10 | 12 | 98,061 | *77,865* | **250,411** | 222 | -21 | 155 | +| 20 | 36 | 32,105 | *21,225* | **130,707** | 516 | -34 | 307 | +| 50 | 243 | *4,289* | 5,504 | **43,065** | 682 | 28 | 904 | +| 100 | 1,536 | *724* | 1,203 | **24,768** | 1,959 | 66 | 3,321 | +| 200 | 16,200 | *63.50* | 272 | **12,470** | 4,485 | 328 | 19,538 | +| 500 | 937,500 | *0.57* | 48 | **4,639** | 9,565 | 8,306 | 812,334 | +| 1,000 | 51,200,000 | *-* | 10.60 | **2,503** | 23,513 | - | - | +| 2,000 | 8,062,156,800 | *-* | 2.75 | **1,187** | 43,064 | - | - | +| 5,000 | 50,837,316,566,580 | *-* | 0.41 | **375** | 91,812 | - | - | +| 10,000 | 288,325,195,312,500,000 | *-* | 0.08 | **230** | 273,710 | - | - | +| 13,282 | 18,432,000,000,000,000,000 | *-* | 0.05 | **148** | 302,757 | - | - | # Task 2 - Square Points |
