Why does my sieve not perform well for finding primes? - javascript

I wrote two prime finder functions and the sieve only performs about 10% better. I'm using two optimizations for the simple version.
Don't check even numbers
Only check up to the square root or j * j <= i. ( equivalent )
and one optimization for the sieve version
Only check up to the square root or i * i <= n. ( equivalent )
What optimizations can I add to the sieve?
My sieve is pretty slow. I don't want to do a bitwise implementation yet, I want to understand if this implementation offers any benefits.
Or if I missed an implementation point.
The inner for loop in the pseudocode here looks interesting / odd
https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
I don't know how to interpret it. (update: the OP seems to indicate in the comments that it was an issue with incorrect formatting after copy-pasting the pseudocode from Wikipedia, and with the corrected formatting it is clear now)
Here it is:
algorithm Sieve of Eratosthenes is: input: an integer n > 1.
output: all prime numbers from 2 through n.
let A be an array of Boolean values, indexed by integers 2 to n,
initially all set to true.
for i = 2, 3, 4, ..., not exceeding √n do
if A[i] is true
for j = i2, i2+i, i2+2i, i2+3i, ..., not exceeding n do
A[j] := false
return all i such that A[i] is true.
// prime-2
// 2 optimizations - odds and square root
function prime2(n){
const primes = [2];
not_prime: for(let i = 3; i < n; i += 2){
for(let j = 2; j * j <= i; j++){
if(i % j === 0){
continue not_prime;
}
}
primes.push(i);
}
return primes;
}
// prime-3
// sieve implementation
function prime3 (n) {
const primes = [];
const sieve = (new Array(n)).fill(true);
for (let i = 2; i * i <= n; i += 1) {
if (sieve[i]) {
for (let j = i + i; j < n; j += i) {
sieve[j] = false;
}
}
}
makePrimes(sieve, primes, n);
return primes;
};
function makePrimes(sieve, primes, n){
for (let i = 2; i < n; i++) {
if(sieve[i]) {
primes.push(i);
}
}
}

What you see is an expression of the differences in theoretical run time complexities, i.e. the true algorithmic differences between the two algorithms.
Optimal trial division sieve's complexity is O(n1.5/(log n)2)(*) whereas the sieve of Eratosthenes' complexity is O(n log log n).
According to the empirical run time figures posted by Scott Sauyet in the comments,
1e6 279ms 36ms
1e7 6946ms 291ms
-------------------------
n^ 1.40 0.91
the empirical orders of growth are roughly ~n1.4 and ~n in the measured range, which is a good fit.
So your genuine sieve does perform well. The trial division sieve performs as expected. The algorithmic nature of a code will always beat any presence or absence of any secondary optimizations, if we increase the problem size enough.
And comparing performances by measuring them at just one problem-size point is never enough. So even if you see just 10% difference over the "simpler one", if you test at bigger sizes, the difference will be bigger.
If you want some pointers about what can be further improved in your code, do note that you start the inner loop from i+i instead of from i*i, for starters.
Another common optimization is to special-case 2, start from 3 and increment the candidates by 2 and use the inner loop increment of 2*i instead of just i, to achieve instant 2x speedup. This is the simplest form of wheel factorization optimization, which can be further applied, with diminishing returns though for each additional prime. But using 2-3-5-7 is common and should give about another 2x speedup, if memory serves.
Last but not least, make it segmented.
(*) that's π(n)* π(√n) coming from primes, and no more than that, from the composites.

Related

Trying to optimize my code to either remove nested loop or make it more efficient

A friend of mine takes a sequence of numbers from 1 to n (where n > 0)
Within that sequence, he chooses two numbers, a and b
He says that the product of a and b should be equal to the sum of all numbers in the sequence, excluding a and b
Given a number n, could you tell me the numbers he excluded from the sequence?
Have found the solution to this Kata from Code Wars but it times out (After 12 seconds) in the editor when I run it; any ideas as too how I should further optimize the nested for loop and or remove it?
function removeNb(n) {
var nArray = [];
var sum = 0;
var answersArray = [];
for (let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
var length = nArray.length;
for (let i = Math.round(n / 2); i < length; i++) {
for (let y = Math.round(n / 2); y < length; y++) {
if (i != y) {
if (i * y === sum - i - y) {
answersArray.push([i, y]);
break;
}
}
}
}
return answersArray;
}
console.log(removeNb(102));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think there is no reason for calculating the sum after you fill the array, you can do that while filling it.
function removeNb(n) {
let nArray = [];
let sum = 0;
for(let i = 1; i <= n; i++) {
nArray.push(i);
sum += i;
}
}
And since there could be only two numbers a and b as the inputs for the formula a * b = sum - a - b, there could be only one possible value for each of them. So, there's no need to continue the loop when you find them.
if(i*y === sum - i - y) {
answersArray.push([i,y]);
break;
}
I recommend looking at the problem in another way.
You are trying to find two numbers a and b using this formula a * b = sum - a - b.
Why not reduce the formula like this:
a * b + a = sum - b
a ( b + 1 ) = sum - b
a = (sum - b) / ( b + 1 )
Then you only need one for loop that produces the value of b, check if (sum - b) is divisible by ( b + 1 ) and if the division produces a number that is less than n.
for(let i = 1; i <= n; i++) {
let eq1 = sum - i;
let eq2 = i + 1;
if (eq1 % eq2 === 0) {
let a = eq1 / eq2;
if (a < n && a != i) {
return [[a, b], [b, a]];
}
}
}
You can solve this in linear time with two pointers method (page 77 in the book).
In order to gain intuition towards a solution, let's start thinking about this part of your code:
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
...
You already figured out this is the part of your code that is slow. You are trying every combination of i and y, but what if you didn't have to try every single combination?
Let's take a small example to illustrate why you don't have to try every combination.
Suppose n == 10 so we have 1 2 3 4 5 6 7 8 9 10 where sum = 55.
Suppose the first combination we tried was 1*10.
Does it make sense to try 1*9 next? Of course not, since we know that 1*10 < 55-10-1 we know we have to increase our product, not decrease it.
So let's try 2*10. Well, 20 < 55-10-2 so we still have to increase.
3*10==30 < 55-3-10==42
4*10==40 < 55-4-10==41
But then 5*10==50 > 55-5-10==40. Now we know we have to decrease our product. We could either decrease 5 or we could decrease 10, but we already know that there is no solution if we decrease 5 (since we tried that in the previous step). So the only choice is to decrease 10.
5*9==45 > 55-5-9==41. Same thing again: we have to decrease 9.
5*8==40 < 55-5-8==42. And now we have to increase again...
You can think about the above example as having 2 pointers which are initialized to the beginning and end of the sequence. At every step we either
move the left pointer towards right
or move the right pointer towards left
In the beginning the difference between pointers is n-1. At every step the difference between pointers decreases by one. We can stop when the pointers cross each other (and say that no solution can be obtained if one was not found so far). So clearly we can not do more than n computations before arriving at a solution. This is what it means to say that the solution is linear with respect to n; no matter how large n grows, we never do more than n computations. Contrast this to your original solution, where we actually end up doing n^2 computations as n grows large.
Hassan is correct, here is a full solution:
function removeNb (n) {
var a = 1;
var d = 1;
// Calculate the sum of the numbers 1-n without anything removed
var S = 0.5 * n * (2*a + (d *(n-1)));
// For each possible value of b, calculate a if it exists.
var results = [];
for (let numB = a; numB <= n; numB++) {
let eq1 = S - numB;
let eq2 = numB + 1;
if (eq1 % eq2 === 0) {
let numA = eq1 / eq2;
if (numA < n && numA != numB) {
results.push([numA, numB]);
results.push([numB, numA]);
}
}
}
return results;
}
In case it's of interest, CY Aries pointed this out:
ab + a + b = n(n + 1)/2
add 1 to both sides
ab + a + b + 1 = (n^2 + n + 2) / 2
(a + 1)(b + 1) = (n^2 + n + 2) / 2
so we're looking for factors of (n^2 + n + 2) / 2 and have some indication about the least size of the factor. This doesn't necessarily imply a great improvement in complexity for the actual search but still it's kind of cool.
This is part comment, part answer.
In engineering terms, the original function posted is using "brute force" to solve the problem, iterating every (or more than needed) possible combinations. The number of iterations is n is large - if you did all possible it would be
n * (n-1) = bazillio n
Less is More
So lets look at things that can be optimized, first some minor things, I'm a little confused about the first for loop and nArray:
// OP's code
for(let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
??? You don't really use nArray for anything? Length is just n .. am I so sleep deprived I'm missing something? And while you can sum a consecutive sequence of integers 1-n by using a for loop, there is a direct and easy way that avoids a loop:
sum = ( n + 1 ) * n * 0.5 ;
THE LOOPS
// OP's loops, not optimized
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
if(i != y) {
if(i*y === sum - i - y) {
Optimization Considerations:
I see you're on the right track in a way, cutting the starting i, y values in half since the factors . But you're iterating both of them in the same direction : UP. And also, the lower numbers look like they can go a little below half of n (perhaps not because the sequence start at 1, I haven't confirmed that, but it seems the case).
Plus we want to avoid division every time we start an instantiation of the loop (i.e set the variable once, and also we're going to change it). And finally, with the IF statements, i and y will never be equal to each other the way we're going to create the loops, so that's a conditional that can vanish.
But the more important thing is the direction of transversing the loops. The smaller factor low is probably going to be close to the lowest loop value (about half of n) and the larger factor hi is probably going to be near the value of n. If we has some solid math theory that said something like "hi will never be less than 0.75n" then we could make a couple mods to take advantage of that knowledge.
The way the loops are show below, they break and iterate before the hi and low loops meet.
Moreover, it doesn't matter which loop picks the lower or higher number, so we can use this to shorten the inner loop as number pairs are tested, making the loop smaller each time. We don't want to waste time checking the same pair of numbers more than once! The lower factor's loop will start a little below half of n and go up, and the higher factor's loop will start at n and go down.
// Code Fragment, more optimized:
let nHi = n;
let low = Math.trunc( n * 0.49 );
let sum = ( n + 1 ) * n * 0.5 ;
// While Loop for the outside (incrementing) loop
while( low < nHi ) {
// FOR loop for the inside decrementing loop
for(let hi = nHi; hi > low; hi--) {
// If we're higher than the sum, we exit, decrement.
if( hi * low + hi + low > sum ) {
continue;
}
// If we're equal, then we're DONE and we write to array.
else if( hi * low + hi + low === sum) {
answersArray.push([hi, low]);
low = nHi; // Note this is if we want to end once finding one pair
break; // If you want to find ALL pairs for large numbers then replace these low = nHi; with low++;
}
// And if not, we increment the low counter and restart the hi loop from the top.
else {
low++;
break;
}
} // close for
} // close while
Tutorial:
So we set the few variables. Note that low is set slightly less than half of n, as larger numbers look like they could be a few points less. Also, we don't round, we truncate, which is essentially "always rounding down", and is slightly better for performance, (though it dosenit matter in this instance with just the single assignment).
The while loop starts at the lowest value and increments, potentially all the way up to n-1. The hi FOR loop starts at n (copied to nHi), and then decrements until the factor are found OR it intercepts at low + 1.
The conditionals:
First IF: If we're higher than the sum, we exit, decrement, and continue at a lower value for the hi factor.
ELSE IF: If we are EQUAL, then we're done, and break for lunch. We set low = nHi so that when we break out of the FOR loop, we will also exit the WHILE loop.
ELSE: If we get here it's because we're less than the sum, so we need to increment the while loop and reset the hi FOR loop to start again from n (nHi).

Shuffling a poker deck in JavaScript with window.crypto.getRandomValues

A poker deck has 52 cards and thus 52! or roughly 2^226 possible permutations.
Now I want to shuffle such a deck of cards perfectly, with truly random results and a uniform distribution, so that you can reach every single one of those possible permutations and each is equally likely to appear.
Why is this actually necessary?
For games, perhaps, you don't really need perfect randomness, unless there's money to be won. Apart from that, humans probably won't even perceive the "differences" in randomness.
But if I'm not mistaken, if you use shuffling functions and RNG components commonly built into popular programming languages, you will often get no more than 32 bits of entropy and 2^32 states. Thus, you will never be able to reach all 52! possible permutations of the deck when shuffling, but only about ...
0.000000000000000000000000000000000000000000000000000000005324900157 %
... of the possible permutations. That means a whole lot of all the possible games that could be played or simulated in theory will never actually be seen in practice.
By the way, you can further improve the results if you don't reset to the default order every time before shuffling but instead start with the order from the last shuffle or keep the "mess" after a game has been played and shuffle from there.
Requirements:
So in order to do what is described above, one needs all of the following three components, as far as I have understood:
A good shuffling algorithm that ensures a uniform distribution.
A proper RNG with at least 226 bits of internal state. Since we're on deterministic machines, a PRNG will be all we'll get, and perhaps this should be a CSPRNG.
A random seed with at least 226 bits of entropy.
Solutions:
Now is this achievable? What do we have?
Fisher-Yates shuffle will be fine, as far as I can see.
The xorshift7 RNG has more than the required 226 bits of internal state and should suffice.
Using window.crypto.getRandomValues we can generate the required 226 bits of entropy to be used as our seed. If that still isn't enough, we can add some more entropy from other sources.
Question:
Are the solutions (and also the requirements) mentioned above correct? How can you implement shuffling using these solutions in JavaScript in practice then? How do you combine the three components to a working solution?
I guess I have to replace the usage of Math.random in the example of the Fisher-Yates shuffle with a call to xorshift7. But that RNG outputs a value in the [0, 1) float range and I need the [1, n] integer range instead. When scaling that range, I don't want to lose the uniform distribution. Moreover, I wanted about 226 bits of randomness. If my RNG outputs just a single Number, isn't that randomness effectively reduced to 2^53 (or 2^64) bits because there are no more possibilities for the output?
In order to generate the seed for the RNG, I wanted to do something like this:
var randomBytes = generateRandomBytes(226);
function generateRandomBytes(n) {
var data = new Uint8Array(
Math.ceil(n / 8)
);
window.crypto.getRandomValues(data);
return data;
}
Is this correct? I don't see how I could pass randomBytes to the RNG as a seed in any way, and I don't know how I could modify it to accep this.
Here's a function I wrote that uses Fisher-Yates shuffling based on random bytes sourced from window.crypto. Since Fisher-Yates requires that random numbers are generated over varying ranges, it starts out with a 6-bit mask (mask=0x3f), but gradually reduces the number of bits in this mask as the required range gets smaller (i.e., whenever i is a power of 2).
function shuffledeck() {
var cards = Array("A♣️","2♣️","3♣️","4♣️","5♣️","6♣️","7♣️","8♣️","9♣️","10♣️","J♣️","Q♣️","K♣️",
"A♦️","2♦️","3♦️","4♦️","5♦️","6♦️","7♦️","8♦️","9♦️","10♦️","J♦️","Q♦️","K♦️",
"A♥️","2♥️","3♥️","4♥️","5♥️","6♥️","7♥️","8♥️","9♥️","10♥️","J♥️","Q♥️","K♥️",
"A♠️","2♠️","3♠️","4♠️","5♠️","6♠️","7♠️","8♠️","9♠️","10♠️","J♠️","Q♠️","K♠️");
var rndbytes = new Uint8Array(100);
var i, j, r=100, tmp, mask=0x3f;
/* Fisher-Yates shuffle, using uniform random values from window.crypto */
for (i=51; i>0; i--) {
if ((i & (i+1)) == 0) mask >>= 1;
do {
/* Fetch random values in 100-byte blocks. (We probably only need to do */
/* this once.) The `mask` variable extracts the required number of bits */
/* for efficient discarding of random numbers that are too large. */
if (r == 100) {
window.crypto.getRandomValues(rndbytes);
r = 0;
}
j = rndbytes[r++] & mask;
} while (j > i);
/* Swap cards[i] and cards[j] */
tmp = cards[i];
cards[i] = cards[j];
cards[j] = tmp;
}
return cards;
}
An assessment of window.crypto libraries really deserves its own question, but anyway...
The pseudorandom stream provided by window.crypto.getRandomValues() should be sufficiently random for any purpose, but is generated by different mechanisms in different browsers. According to a 2013 survey:
Firefox (v. 21+) uses NIST SP 800-90 with a 440-bit seed. Note: This standard was updated in 2015 to remove the (possibly backdoored) Dual_EC_DRBG elliptic curve PRNG algorithm.
Internet Explorer (v. 11+) uses one of the algorithms supported by BCryptGenRandom (seed length = ?)
Safari, Chrome and Opera use an ARC4 stream cipher with a 1024-bit seed.
Edit:
A cleaner solution would be to add a generic shuffle() method to Javascript's array prototype:
// Add Fisher-Yates shuffle method to Javascript's Array type, using
// window.crypto.getRandomValues as a source of randomness.
if (Uint8Array && window.crypto && window.crypto.getRandomValues) {
Array.prototype.shuffle = function() {
var n = this.length;
// If array has <2 items, there is nothing to do
if (n < 2) return this;
// Reject arrays with >= 2**31 items
if (n > 0x7fffffff) throw "ArrayTooLong";
var i, j, r=n*2, tmp, mask;
// Fetch (2*length) random values
var rnd_words = new Uint32Array(r);
// Create a mask to filter these values
for (i=n, mask=0; i; i>>=1) mask = (mask << 1) | 1;
// Perform Fisher-Yates shuffle
for (i=n-1; i>0; i--) {
if ((i & (i+1)) == 0) mask >>= 1;
do {
if (r == n*2) {
// Refresh random values if all used up
window.crypto.getRandomValues(rnd_words);
r = 0;
}
j = rnd_words[r++] & mask;
} while (j > i);
tmp = this[i];
this[i] = this[j];
this[j] = tmp;
}
return this;
}
} else throw "Unsupported";
// Example:
deck = [ "A♣️","2♣️","3♣️","4♣️","5♣️","6♣️","7♣️","8♣️","9♣️","10♣️","J♣️","Q♣️","K♣️",
"A♦️","2♦️","3♦️","4♦️","5♦️","6♦️","7♦️","8♦️","9♦️","10♦️","J♦️","Q♦️","K♦️",
"A♥️","2♥️","3♥️","4♥️","5♥️","6♥️","7♥️","8♥️","9♥️","10♥️","J♥️","Q♥️","K♥️",
"A♠️","2♠️","3♠️","4♠️","5♠️","6♠️","7♠️","8♠️","9♠️","10♠️","J♠️","Q♠️","K♠️"];
deck.shuffle();
Combining this answer from here with this answer from another question, it seems the following could be a more general and modular (though less optimized) version:
// Fisher-Yates
function shuffle(array) {
var i, j;
for (i = array.length - 1; i > 0; i--) {
j = randomInt(0, i + 1);
swap(array, i, j);
}
}
// replacement for:
// Math.floor(Math.random() * (max - min)) + min
function randomInt(min, max) {
var range = max - min;
var bytesNeeded = Math.ceil(Math.log2(range) / 8);
var randomBytes = new Uint8Array(bytesNeeded);
var maximumRange = Math.pow(Math.pow(2, 8), bytesNeeded);
var extendedRange = Math.floor(maximumRange / range) * range;
var i, randomInteger;
while (true) {
window.crypto.getRandomValues(randomBytes);
randomInteger = 0;
for (i = 0; i < bytesNeeded; i++) {
randomInteger <<= 8;
randomInteger += randomBytes[i];
}
if (randomInteger < extendedRange) {
randomInteger %= range;
return min + randomInteger;
}
}
}
function swap(array, first, second) {
var temp;
temp = array[first];
array[first] = array[second];
array[second] = temp;
}
I personally think you could move outside the box a little bit. If you're that worried about randomness, you could look into an API key from random.org ( https://api.random.org/json-rpc/1/ ) or parse it out of a link like this: https://www.random.org/integer-sets/?sets=1&num=52&min=1&max=52&seqnos=on&commas=on&order=index&format=html&rnd=new .
Sure, your datasets could be intercepted, but if you get a few hundred thousand sets of them then shuffle those sets you would be fine.

Max length of collatz sequence - optimisation

I'm trying to solve this MaxCollatzLength kata but I'm struggling to optimise it to run fast enough for really large numbers.
In this kata we will take a look at the length of collatz sequences.
And how they evolve. Write a function that take a positive integer n
and return the number between 1 and n that has the maximum Collatz
sequence length and the maximum length. The output has to take the
form of an array [number, maxLength] For exemple the Collatz sequence
of 4 is [4,2,1], 3 is [3,10,5,16,8,4,2,1], 2 is [2,1], 1 is [ 1 ], so
MaxCollatzLength(4) should return [3,8]. If n is not a positive
integer, the function have to return [].
As you can see, numbers in Collatz sequences may exceed n. The last
tests use random big numbers so you may consider some optimisation in
your code:
You may get very unlucky and get only hard numbers: try submitting 2-3
times if it times out; if it still does, probably you need to optimize
your code more;
Optimisation 1: when calculating the length of a
sequence, if n is odd, what 3n+1 will be ?
Optimisation 2: when looping through 1 to n, take i such that i < n/2, what
will be the length of the sequence for 2i ?
A recursive solution quickly blows the stack, so I'm using a while loop. I think I've understood and applied the first optimisation. I also spotted that for n that is a power of 2, the max length will be (log2 of n) + 1 (that only shaves off a very small amount of time for an arbirtarily large number). Finally I have memoised the collatz lengths computed so far to avoid recalculations.
I don't understand what is meant by the second optimisation, however. I've tried to notice a pattern with a few random samples and loops and I've plotted the max collatz lengths for n < 50000. I noticed it seems to roughly follow a curve but I don't know how to proceed - is this a red herring?
I'm ideally looking for a hints in the right direction so I can work towards the solution myself.
function collatz(n) {
let result = [];
while (n !== 1) {
result.push(n);
if (n % 2 === 0) n /= 2;
else {
n = n * 3 + 1;
result.push(n);
n = n / 2;
}
}
result.push(1);
return result;
}
function collatzLength(n) {
if (n <= 1) return 1;
if (!collatzLength.precomputed.hasOwnProperty(n)) {
// powers of 2 are logarithm2 + 1 long
if ((n & (n - 1)) === 0) {
collatzLength.precomputed[n] = Math.log2(n) + 1;
} else {
collatzLength.precomputed[n] = collatz(n).length;
}
}
return collatzLength.precomputed[n];
}
collatzLength.precomputed = {};
function MaxCollatzLength(n) {
if (typeof n !== 'number' || n === 0) return [];
let maxLen = 0;
let numeralWithMaxLen = Infinity;
while (n !== 0) {
let lengthOfN = collatzLength(n);
if (lengthOfN > maxLen) {
maxLen = lengthOfN;
numeralWithMaxLen = n;
}
n--;
}
return [numeralWithMaxLen, maxLen];
}
Memoization is the key to good performance here. You memoize the end results of the function that calculates the Collatz sequence. This will help you on repeated calls to maxCollatzLength, but not when you determine the length of the sequence for the first time.
Also, as #j_random_hacker mentioned, there is no need to actually create the sequence as list; it is enough to store its length. An integer result is light-weight enough to be memoized easily.
You can make use of precalculated results already when you determine the length of a Collatz sequence. Instead of following the sequence all the way down, follow it until you hit a number for which the length is known.
The other optimizations you make are micro-optimizations. I'm not sure that calculating the log for powers of two really buys you anything. It rather burdens you with an extra test.
The memoized implementation below even forgoes the check for 1 by putting 1 in the dictionary of precalculated values initially.
var precomp = {1: 1};
function collatz(n) {
var orig = n;
var len = 0;
while (!(n in precomp)) {
n = (n % 2) ? 3*n + 1 : n / 2;
len++;
}
return (precomp[orig] = len + precomp[n]);
}
function maxCollatz(n) {
var res = [1, 1];
for (var k = 2; k <= n; k++) {
var c = collatz(k);
if (c > res[1]) {
res[0] = k;
res[1] = c;
}
}
return res;
}
I haven't used node.js, but the JavaScript in my Firefox. It gives reasonable performance. I first had collatz as a recursive function, which made the implementation only slightly faster than yours.
The second optimization mentioned in the question means that if you know C(n), you also know that C(2*n) == C(n) + 1. You could use that knowledge to precalculate the values for all even n in a bottom-up approach.
It would be nice if the lengths of the Collatz sequences could be calculated from the bottom up, a bit like the sieve of Erathostenes. You have to know where you come from instead of where you go to, but it is hard to know ehen to stop, because for finding the longest sequence for n < N, you will have to calculate many sequences out of bound with n > N. As is, the memoization is a good way to avoid repetition in an otherwise straightforwad iterative approach.
In this task you are required to write a Python function,
maxLength, that returns two integers:
• First returned value: for each integer k, 1 ≤ k ≤ m, the
length of Collatz sequence for each k is computed and the
largest of these numbers is returned.
• Second returned value is the integer k, 1 ≤ k ≤ m, whose
Collatz sequence has the largest length. In case there are
several such numbers, return the first one (the smallest).
For example, maxLength(10) returns numbers
20 and 9
Which means that among the numbers 1, 2, 3,…, 10, nine has the
longest Collatz sequence, and its length is equal to 20.
In your program you may define other (auxiliary) functions with
arbitrary names, however, the solution function of this task
should be named maxLength(m).

How would you implement a O(C^n) time complexity function using for loops?

The only thing I can think of uses Math.pow() to calculate n before the loop and seems like a cop out.
C=2 in this example:
var oCN = function (n)
{
var j = Math.pow(2, n);
for (var i = 0; i <= j; i++) {
console.log('cactus');
};
return;
}
A recursive algorithm like this one would grow exponentially with respect to n:
var oCN = function (C, n)
{
if (n < 1) { console.log('cactus'); return; }
for (var i = 0; i < C; i++) {
oCN(C, n-1);
}
}
The number of logged cacti is in the case of this algorithm is exactly C^n, which is of course O(C^n). Eg. oCN(2, 4) logs cactus 16 times, oCN(2, 5) logs it 32 times, etc.
You won't typically see a lot of day-to-day algorithms that work in exponential time. If something is being done in exponential time there is a good chance the input size or constants are such that the big-O complexity doesn't matter very much at all (or at least, the implementer doesn't care very much), and so you probably won't be sitting around analyzing the complexity. Most normal stuff programmers want to do can either be done with a polynomial time algorithm right off the bat or with some fudging and sacrificing.

What does this line mean in a getPrimes algorithm?

var fs = require('fs');
var outfile = "primes.txt";
function getPrimes(max) {
var primeSieve = [], i, j, primes = [];
for (i = 2; i <= max; ++i) {
if (!primeSieve[i]) {
// i has not been marked - it is prime
primes.push(i);
for (j = i << 1; j <= max; j += i) {
primeSieve[j] = true;
}
}
}
return primes;
}
fs.writeFileSync(outfile, getPrimes(1000).slice(0,100) + ",");
console.log("Script: " + __filename + "\nWrote: " + getPrimes(1000).slice(0,100) + "To: " + outfile);
I have the above piece of code that I modified to produce an output (the main algorithm provided by someone else). I am new to Javascript and am unsure of what the following line is actually doing and what the << operator means (I have been unable to find out on the Javascript website).
for (j = i << 1; j <= max; j += i)
I know that it is marking the relevant numbers in the main primeSieve array as true so that they do not populate the primes array, however I don't know how it is doing this.
The << operator is the left shift operator. The left argument (after conversion to an integer value, if necessary) is shifted to the left by the number of bits specified by the right argument, right-filling with zeroes. Shifting left by one is the same as multiplying by 2.
The inner loop simply stores true in every element of primeSieve that is at an index that is a multiple of i. Thus, if primeSieve[j] is true, then j must be a multiple of some previous i (hence j cannot be prime). Conversely, if primeSieve[i] is not true, then it was not a multiple of any previous value of i; since that includes all integers from 2 to i-1, i must then be prime.
For collecting all primes up to a certain maximum, this method is far superior to techniques that independently test each integer for primality. However, it is far from the most efficient method. For instance, note that an element of primeSieve might get set to true several times. For instance, primeSieve[6] is set when i==2 and again when i==3. Also, once i exceeds the square root of max, the inner loop is a waste, since all composite numbers up to max are guaranteed to have been marked at that point. See the Wikipedia article on the Sieve of Eratosthenes for more about how this all works and pointers to even more efficient methods.
P.S. That code looks suspiciously familiar. :-)

Categories

Resources