Using recursion in JavaScript to determine if a given number is prime

I am feeling a bit puzzled about how to tackle this issue. My goal is to have all prime numbers return as true, and if not, then false. I noticed that my current logic includes 2, which returns 0 and automatically results in false because 2 has a remainder of 0.

  

  function checkIfPrime(num, div = 2) {
      // BASE CASE: 
     
      if(num <= div ) return false; // If the number is less than or equal to 2, return false 
      
      // If there is no remainder when dividing by 2   
         
      if(num % 2 === 0) return false  // Return false 
      
      return true; // Return true
     
      // Recursive call:

      return checkIfPrime(num)
    }

    console.log(checkIfPrime(1)); //-> false
    console.log(checkIfPrime(2)); //-> true
    console.log(checkIfPrime(3)); //-> true
    console.log(checkIfPrime(4)); //-> false

Answer №1

To achieve optimal performance, avoid recursion altogether. Instead, test all odd integers from 3 to the square root of the number as potential factors.

function isPrime(num){
      if(num === 2) return true;
      if(num < 2 || num % 2 === 0) return false;
      for(let i = 3; i * i <= num; i += 2)
          if(num % i === 0) return false;
      return true;
}

In case you must utilize recursion, you can implement the same concept by introducing a factor as the second argument and increasing it by 2 during recursive calls.

function isPrime(num, div = 3){
      if(num === 2) return true;
      if(num < 2 || num % 2 === 0)  return false;
      if(div * div > num) return true;
      if(num % div === 0) return false;
      return isPrime(num, div + 2);
}

Answer №2

I initially came here for the discussion on recursion, but stumbled upon an excellent solution provided by Thankyou using some clever abstractions.

The approach utilized in that response involved a variation of the Sieve of Eratosthenes. However, I'd like to introduce an alternate version of this sieve which may lack elegance but offers improved performance.

Here is the primary implementation as a generator function:

const sieve = function * () {
    const C = {} // store known composite numbers
    let q = 2
    while (q < Infinity) {
        if (C [q]) {
            C [q] .forEach (n => C [n + q] = [... (C [n + q] || []), n]  )
            delete C [q]
        } else {
            C [2 * q] = [q]
            yield q
        }
        q += 1
    }
}

The traditional Sieve method involves eliminating all multiples of a prime number. While this works well with predefined lists of numbers, we need to handle continuous streams without a fixed upper limit. Therefore, instead of crossing off all multiples at once, we only eliminate the next multiple to be encountered. This is tracked using the local variable C, which contains information about prime multiples.

For instance, if we reach a test value of 11, we have already identified primes such as 2, 3, 5, and 7. The subsequent multiple of 2 is 12; for 3, it's also 12, followed by 15 for 5 and 14 for 7. At this stage, C will appear as shown below:

{
  12: [3, 2],
  14: [7],
  15: [5]
}

If 11 does not exist in this list, we include 2 * 11 in C and yield it as the next prime. So, when assessing 12, C updates to:

{
  12: [3, 2],
  14: [7],
  15: [5],
  22: [11]
}

Since 12 is now part of this object, it is deemed non-prime. Subsequently, adjustments must be made. Removing 12 from the list necessitates placing our 2 and 3 values on their succeeding multiples. For example, following 12, the next multiple of 2 is

14</code, so we update the value at <code>14
to contain 2. Similarly, after 12, the subsequent multiple of 3 is 15, prompting us to amend the value at 15</code to involve <code>3.

As a result, when we encounter 13, C appears as:

{
  14: [7, 2],
  15: [5, 3],
  22: [11]
}

Given that 13 is absent in C, we identify it as prime, yielding it and adding 26 (2 * 13) to

C</code alongside the value <code>13
. This process leads to an ongoing sequence of prime numbers, maintaining the collection of each prime's upcoming multiples.

An astute observer might notice some excess activity within this methodology. Specifically, when introducing the initial multiple of, say, 7 to our list, commencing at

14</code isn't essential since it's already marked off as a multiple of <code>2
. Likewise, 21 represents a multiple of 3, 28 is tied to 4 (and consequently 2), 35 is linked to 5, followed by
42</code denoting a multiple of <code>6
(including 2 and 3). The first specific multiple that requires verification is 49 whenever a new prime like p is added; the initial relevant multiple is p ** 2. One can rectify this quirk by replacing:

            C [2 * q] = [q]

with:

            C [q * q] = [q]

To validate this function, one may execute tests as follows:

const iterator = sieve()

console.log(iterator.next()) //=> {value: 2, done: false}
console.log(iterator.next()) //=> {value: 3, done: false}
console.log(iterator.next()) //=> {value: 5, done: false}
console.log(iterator.next()) //=> {value: 7, done: false}
console.log(iterator.next()) //=> {value: 11, done: false}
console.log(iterator.next()) //=> {value: 13, done: false}

One could implement a take function akin to the one found in Thankyou's explanation, which is likely the optimal approach. Alternatively, you can modify this function to terminate either after finding the first n primes or upon exceeding the value of n.

The latter option merely entails incorporating the parameter max and substituting instances of Infinity accordingly. Conversely, the former scenario presents a slightly greater challenge. Here is one iteration of this concept:

const sieve = function * (count) {
    const C = {} // known composite numbers
    let n = 0
    let q = 2
    while (n < count) {
         if (C [q]) {
            C [q] .forEach (n => C [n + q] = [... (C [n + q] || []), n]  )
            delete C [q]
        } else {
            C [q * q] = [q]
            n += 1
            yield q
        }
        q += 1
    }
};

console .log ([... sieve(1000)])
.as-console-wrapper {max-height: 100% !important; top: 0}

In a more logical context, our internal composites data structure should ideally abandon Object mappings in favor of integer-to-integer array conversion. A more suitable alternative would entail utilizing a Map conforming to integers paired with Sets of integers. Such an implementation is feasible:

const sieve = function * (count) {
    const C = new Map () // known composite numbers
    let n = 0
    let q = 2
    while (n < count) {
        if (C .has (q)) {
            [... C .get (q)] .forEach (
              n => C .set (n + q, (C .get (n + q) || new Set ()) .add (n))
            )
            C .delete (q)
        } else {
            C .set (q * q, new Set ([q]))
            n += 1;
            yield q
        }
        q += 1
    }
};

Although somewhat less readable compared to its Object-Array counterpart, this implementation operates identically. Performance assessments are pending, though no significant speed variations are anticipated when transitioning between structures. Regardless, both methods remain effective.

This narrative deviates from my typical style. Although steering clear of inner structure mutations via recursive means is plausible, no readily apparent technique comes to mind.


Additional insights into this rendition of the Sieve, along with discussions on its relative complexity, can be explored within Melissa E. O’Neill's exceptional piece titled The Genuine Sieve of Eratosthenes. Although academically inclined, the paper retains readability and comprehension, particularly for those familiar with Haskell.

Answer №3

Exploring the concept of recursion can be quite enjoyable! Imagine utilizing an emptyStream, having the ability to create new streams, and being able to filter a stream -

import { stream, emptyStream, filter } from "./Stream"

const numbers = (n = 0) =>
  stream(n, _ => numbers(n + 1))

const sieve = (t = numbers(2)) =>
  t === emptyStream
    ? t
    : stream
        ( t.value
        , _ =>
            sieve
              ( filter
                  ( t.next
                  , v => v % t.value !== 0
                  )
              )
        )

This methodology is referred to as the Sieve of Eratosthenes. By incorporating additional stream functions, we aim to take the initial 1,000 items and then convert the resulting sequence into an array with toArray -

import { take, toArray } from "./Stream"

const result = 
  toArray(take(sieve(), 1000))
  
console.log(JSON.stringify(result))
// [2,3,5,7,11,13,17,19,23, ... ,7901,7907,7919]

You have the flexibility to implement the Stream however you desire. Below showcases one potential implementation -

// Stream.js

const emptyStream =
  Symbol('emptyStream')

const stream = (value, next) =>
  ( { value
    , get next ()
      { delete this.next
      ; return this.next = next()
      }
    }
  )

const filter = (t = emptyStream, f = identity) =>
  t === emptyStream
    ? t
: Boolean(f(t.value))
    ? stream
        ( t.value
        , _ => filter(t.next, f)
        )
: filter(t.next, f)

const take = (t = emptyStream, n = 0) =>
  t === emptyStream || n <= 0
    ? emptyStream
    : stream(t.value, _ => take(t.next, n - 1))
    
const toArray = (t = emptyStream) =>
  t === emptyStream
    ? []
    : [ t.value, ...toArray(t.next) ]

export { emptyStream, stream, filter, take, toArray }

Expand the snippet below in your browser to compute the first 1,000 prime numbers -

// Stream.js
const emptyStream =
  Symbol('emptyStream')

const stream = (value, next) =>
  ( { value
    , get next ()
      { delete this.next
      ; return this.next = next()
      }
    }
  )
  
const filter = (t = emptyStream, f = identity) =>
  t === emptyStream
    ? t
: Boolean(f(t.value))
    ? stream
        ( t.value
        , _ => filter(t.next, f)
        )
: filter(t.next, f)

const take = (t = emptyStream, n = 0) =>
  t === emptyStream || n <= 0
    ? emptyStream
    : stream(t.value, _ => take(t.next, n - 1))
    
const toArray = (t = emptyStream) =>
  t === emptyStream
    ? []
    : [ t.value, ...toArray(t.next) ]

// Main.js
const numbers = (n = 0) =>
  stream(n, _ => numbers(n + 1))

const sieve = (t = numbers(2)) =>
  t === emptyStream
    ? t
    : stream
        ( t.value
        , _ =>
            sieve
              ( filter
                  ( t.next
                  , v => v % t.value !== 0
                  )
              )
        )

const result = 
  toArray(take(sieve(), 1000))
  
document.body.textContent = result.join(", ")

Output

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271...

If the previous program caused a stack overflow, no worries, separating our streams using a module allows us a level of abstraction. This means modifications made to the Stream won't impact our Main module. For larger computations, consider updating the offenders to utilize a while loop instead of recursion -

// Stream.js

const emptyStream = //...

const stream = //...

const take = // ...

function filter (t = emptyStream, f = identity)
{ while (t !== emptyStream)
    if (Boolean(f(t.value)))
      return stream
        ( t.value
        , _ => filter(t.next, f)
        )
    else
      t = t.next      // <-- advance stream t without recursion
  return t
}

function toArray (t = emptyStream)
{ let r = []
  while (t !== emptyStream)
    ( r.push(t.value)
    , t = t.next      // <-- advance stream t without recursion
    )
  return r
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I am facing an issue with the Tailwind Flowbite Datepicker dropdown UI when running "npm run prod" for minification. The class is not being added during minification on the npm

I have successfully integrated a Laravel project with Tailwind CSS. I have also implemented the Flowbite Datepicker using a CDN to include the necessary JavaScript. Initially, everything was working fine and the date-picker was displaying correctly. Howev ...

The API call for /api/users/create was resolved without a response, which could potentially lead to requests getting stuck. This issue was detected in

I've developed an API endpoint to manage user account creation within my Next.js application, utilizing knex.js for handling queries. Despite this, I keep encountering the following error: API resolved without sending a response for /api/users/create ...

You can easily dismiss the modal by clicking on any part of the screen, not just the button

I have a problem with my current code where I can only close a modal by clicking on a specific button. I want to modify it so that the modal can be closed by clicking anywhere on the screen. Unfortunately, as a JavaScript beginner, integrating new code sni ...

Enhance the code for updating the content within a div element

I recently discovered that utilizing jQuery allows for updating the content within a div. My goal now is to enhance this script so the content remains consistent, even while loading or not. Take a look at my current code: function changeContent () { va ...

What is the best way to trigger an AJAX request when a user navigates away from a webpage or closes the

The Challenge at Hand Greetings, I am currently developing a database-driven game that involves users answering questions and earning the right to change the question by providing the correct answer. However, I have encountered a significant issue which ...

D3 group of legendary elements

Is there a way to group both the circle and text elements for each legend item together? I'm currently facing a challenge with the enter/exit methods. While I can create the groups (g), I'm unable to append the text and circle elements. li.sel ...

What are some ways to streamline and improve the readability of my if/else if statement?

I've created a Rock Paper Scissors game that runs in the console. It currently uses multiple if/else if statements to compare user input against the computer's selection and determine a winner. The code is quite lengthy and repetitive, so I' ...

Encountering the following error: Exceeded maximum call stack size limit

Currently, I am attempting to tackle a specific problem on LeetCode. This particular challenge involves calculating the power of x. However, upon executing my solution, an error message is displayed: RangeError: Maximum call stack size exceeded Here&apos ...

Issue with Masonry.js implementation causing layout to not display correctly

Currently, I am working on a project using Laravel, VueJS, and the Masonry.js library to develop a dynamic gallery. However, I have encountered a peculiar issue. Here is a snippet of my VueJS template: <template lang="html"> <div id="uploads-g ...

Is it possible for a function within a nodejs module to be defined but display as undefined upon access?

I am currently developing a Discord bot using NodeJS and TypeScript, and I'm facing an issue while trying to import custom modules in a loop with the following code: const eventFiles = fs.readdirSync("./src/events/").filter((file: string) =& ...

Enhancing NodeJS performance when manipulating arrays

I'm seeking a way to retrieve a user's chat history with other users from a separate collection in NodeJS and MongoDB. I have concerns about the potential performance impact of running the code below due to the nature of NodeJS. While I could d ...

Dealing with the issue of asynchronous operations in a controller using async/await function

Something strange is happening here, even though I'm using async await: const Employee = require('../models/employee'); const employeeCtrl = {}; employeeCtrl.getEmployees = async (req, res) => { const employees = await Employee.find( ...

The 'file' property of undefined throws an error in ng-file-upload

I am currently exploring the functionality of ng-file-upload from this repository: https://github.com/danialfarid/ng-file-upload I have successfully implemented the basic setup as follows: HTML: <section ng-controller="MyController"> ...

Creating JSON from identical user interface components

I have created a form similar to this one: https://jsfiddle.net/6vocc2yn/ that generates a JSON output like below: { "List": [ { "Id": 10, "Name": "SDB_SOLOCHALLENGE_CHALLENGE_DESC_10", "email": "<a href="/cdn-cgi/l/email-pr ...

"Enhancing User Experience with Hover States in Nested React Menus

I have created a nested menu in the following code. My goal is to dynamically add a selected class name to the Nav.Item element when hovering, and remove it only when another Nav.Item is hovered over. I was able to achieve this using the onMouseOver event. ...

How can I display a calendar with a complete month view using ng-repeat?

I was trying to replicate a table similar to the one shown in this image: (disregard the styling). I am struggling with how to properly format the data to create a similar table in HTML. $scope.toddlers = [ { "name": "a", "day": 1, "total": 3 }, { ...

The 'click' event is not triggering after adding elements to the DOM using AJAX

$(".btn-close").on('click', function () { alert('click'); var win = $(this).closest("div.window"); var winID = win.attr("id"); $(win).find("*").each(function () { var timerid = $(this).attr("data-timer-id"); ...

Adjust the alignment of an expansion panel header to the right using Vuetify

Incorporating the Vuetify expansion panel, I am aiming to align a specific portion of the content within the <div slote="head"></div> to the right, resulting in this desired layout: https://i.sstatic.net/tbOL8.jpg https://i.sstatic.net/22NTS. ...

Send information to a function until the array reaches its maximum length

I am facing a challenge where I have a function that accepts multiple arrays as arguments, but the data available to me is already within an array called mainArr. This mainArr consists of several array items that need to be passed as arguments to the funct ...

Transferring event arguments to JavaScript in ASP.NET C# through OnClientClick

Currently, I have an asp button on my webpage. In the code behind within the Page_Load method, I am assigning some JavaScript calls in the following manner. btnExample.OnClientClicking = "functionOne(1,2);"+"function2()"; However, I am encountering a pro ...