Skip to content

Instantly share code, notes, and snippets.

@nicolegooden
Last active January 1, 2021 20:03
Show Gist options
  • Save nicolegooden/1e8a2e56d085bde5aafa9998cee743a8 to your computer and use it in GitHub Desktop.
Save nicolegooden/1e8a2e56d085bde5aafa9998cee743a8 to your computer and use it in GitHub Desktop.
A Code Wars solution with a focus on optimization

Instructions

Define a function that takes one integer argument and returns logical value true or false depending on if the integer is a prime.

Per Wikipedia, a prime number (or a prime) is a natural number greater than 1 that has no positive divisors other than 1 and itself.

Requirements

You can assume you will be given an integer input.

You can not assume that the integer will be only positive. You may be given negative numbers as well (or 0).

NOTE on performance: There are no fancy optimizations required, but still the most trivial solutions might time out. Numbers go up to 2^31 (or similar, depends on language version). Looping all the way up to n, or n/2, will be too slow.

My Pseudocode

Input: integer

Output: boolean based on whether or not input is prime

Edge Cases:

  • Account for negative numbers
  • Account for 0

Assumption: input is always an integer

Criteria: num is prime IF greater than 1 && there are no positive divisors other than 1 and num

Initial Pseudocode:

IF num is greater than 1 => perform the following:

  • divide num by every number less than its value
  • IF result is a positive number that is neither 1 nor num and is without a remainder, return false out of function (num is not prime)
  • ELSE IF => when down to lowest value closest to 1 => return true out of function (num is prime)

ELSE => return false out of function

All solutions passed sample test suite provided by Codewars

Attempt 1

function isPrime(num) {
  if (num > 1) {
    for (let i = num - 1; i >= 0; i--) {
      if (num % i === 0 && i !== num && i !== 1) {
        return false
      } else if (i === 1) {
        return true
      }
    }
  } else {
    return false
  }
}

Runtime for tests: 283 ms (yikes, definitely timed out!)

Refactor needed due to timeout. Given this function is using a for loop to decrement by 1 from very large numbers (determined by tests), it takes an incredibly long time to run this code against the test suite.

Considering what I know about division, the input divided by 1 will lead to a quotient that is equal to the input itself. The next possible number to divide by is 2. At this point, I realized there was never a need to decrement by 1 from the num - 1 as i, instead, I could cut that runtime in half by reducing the size of the loop by 50%. This is done with the following change to the initial value of i in the for loop below.

Attempt 2

function isPrime(num) {
  if (num > 1) {
    for (let i = Math.floor(num / 2); i >= 1; i--) {
      if (num % i === 0 && i !== num && i !== 1) {
        return false
      } else if (i === 1) {
        return true
      }
    }
  } else {
    return false
  }
}

Runtime for tests: 139 ms (still timed out by server, but progress has been made!)

Given the still-excessive runtime of 139 ms, I decided it was time to switch up my approach. For my third attempt at the solution, I aimed to create an array that contained any necessary divisors for the input. This array needed to have a fixed length because looping through it could not lead to a timeout from the server. As I refactored my solution to fit this need, I acknowledged that the test suite was more likely to fail some unit tests when inputs were very large, leading to false positives (non-prime numbers were determined prime).

Attempt 3

function isPrime(num) {
if (num > 1) {
    const divisors = [2, 3, 4, 5, 6, 7, 8, 9, 10]
    let output;
    divisors.forEach((digit, i) => {
      if (num % digit === 0 && num !== digit) {
        output = false
      } else if (i === divisors.length - 1 && output === undefined) {
        output = true
      }
    })
    return output
  } else {
    return false
  }
}

Runtime for tests: 7 ms (hooray! - failing tests with giant integer inputs, though)

The Big O complexity for these algorithms would be O(n) considering the for loop in the first and second attempts, and the forEach in the third attempt. It is often stated that Big O should not be considered for small problems like these, but where else can you start focusing on Big O if you are not yet working on a company's production code?

My focus on Big O and performance for this challenge has allowed me to further optimize my solutions and strategize for the worst case scenario. My third attempt has a significantly better runtime, but fails to account for the edge case of very large numbers. Any and all ideas for Attempt 4 are welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment