aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Smith <baggy@baggy.me.uk>2021-07-27 07:21:46 +0100
committerGitHub <noreply@github.com>2021-07-27 07:21:46 +0100
commit77314f65a59cf5bbc286fb0e3a0b13f46d852f6e (patch)
treebce63d5914b665545ede7a12369d89159657c640
parent4f8ee21584432dc56c9df610b40b5dc921135f54 (diff)
downloadperlweeklychallenge-club-77314f65a59cf5bbc286fb0e3a0b13f46d852f6e.tar.gz
perlweeklychallenge-club-77314f65a59cf5bbc286fb0e3a0b13f46d852f6e.tar.bz2
perlweeklychallenge-club-77314f65a59cf5bbc286fb0e3a0b13f46d852f6e.zip
Update README.md
-rw-r--r--challenge-123/james-smith/README.md62
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