0
\$\begingroup\$

I was working on a code challenge with the following description:

Given an array of positive or negative integers

I= [i1,..,in]

you have to produce a sorted array P of the form

[ [p, sum of all ij of I for which p is a prime factor (p positive) of ij] ...]

Example: I = [12, 15]; //result = [[2, 12], [3, 27], [5, 15]]

So I created an algorithm that does exactly that, but I have some performance issues. On the code challenge website (Codewars), my algorithm times out for fairly large integers, 173471 being a specific example.

One issue I am aware of with the algorithm and want to improve is that my current method for generating primes simply takes the largest number in the input array, and generates primes up to that number, but rarely do I need that many primes. I have not found a full proof way of creating the full list of possible primes otherwise, however. A second issue is that I have a loop running within my loop, to check which numbers in the input have factors in the list of primes I generated. If I could somehow reduce it to one loop, that would reduce the complexity.

Anyway all of that aside, here is my algorithm:

// Function that returns a collection of primes up to a given number
const sieveOfEratosthenes = (int) => {
  // Initialize some values
  const primes = [...Array(int + 1).keys()].slice(2);
  const sqrtCeil = Math.sqrt(int);
  let p = primes[0];
  let pIndex = 0;
  let factor = 2;

  // Until index refers to the last element, sieve primes
  while (primes[pIndex] < sqrtCeil) {
    const productIndex = primes.indexOf(factor * p);

    if (productIndex > -1) {
      primes.splice(productIndex, 1);
    }

    factor += 1;

    if (factor * p > primes[primes.length - 1]) {
      factor = 2;
      pIndex += 1;
      p = primes[pIndex];
    }
  }

  return primes;
};

function sumOfDivided(lst) {
  // If the input is empty, return an empty list
  if (lst.length === 0) {
    return [];
  }

  // Generate primes with a ceiling of the highest number in the input,
  // accounting for negative numbers
  const primes = sieveOfEratosthenes(
    Math.max(...lst.map((int) => (int < 0 ? int * -1 : int)))
  );

  // Reduce primes array to array of tuples, value one being being the prime,
  // value two being the sum of numbers it was a prime factor for in the input
  return primes.reduce((acc, prime) => {
    // Initialize values
    let sum = 0;
    let primeFactorCount = 0;

    // Check if each integer in the input had this prime as a factor
    for (let int of lst) {
      if (prime > int && int > 0) {
        continue;
      }

      if (int % prime === 0) {
        sum += int;
        primeFactorCount += 1;
      }
    }

    // If at least one number matched, add the tuple to the array
    if (primeFactorCount > 0) {
      return [...acc, [prime, sum]];
    }

    return acc;
  }, []);
}
```
\$\endgroup\$
2
  • \$\begingroup\$ Running your example the slowness is caused by your sieve implementation, which does indeed look a bit funny to me and is far slower than it is supposed to be. I would imagine, though JS does do some clever tricks so it's hard to say, that it's due to primes.splice(productIndex, 1). I would suggest finding a more typical implementation \$\endgroup\$ Commented Mar 26, 2020 at 19:47
  • \$\begingroup\$ I think you are probably right, the array manipulation seems to ruin the performance. \$\endgroup\$ Commented Mar 26, 2020 at 21:43

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.