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

Nuxt 3.11 - Best Practices for Integrating the `github/relative-time-element` Dependency

I'm encountering some difficulties while attempting to integrate github/relative-time-element with Nuxt 3.11.2 and Nitro 2.9.6. This is my current progress: I added the library through the command: $ npm install @github/time-elements. I adjusted nux ...

Guide to switching from test mode to live mode and enabling live mode in stripe with nodejs

I have encountered an issue with the stripe form I am currently using for payments. When the form is loading, it displays "test mode" in the top right corner. I am unsure how to switch it to live mode and cannot find any option on the stripe dashboard to d ...

Incorrect Reactjs installation technique

When I try to run create-react-app on my Windows PC, only dependencies are being installed with no folders other than node_modules. Even when using Yarn, I haven't been able to progress further. Please assist and thank you in advance for any help. Thi ...

My AJAX requests do not include any custom headers being sent

I'm facing an issue with making an AJAX request from my client to my NodeJS/ExpressJS backend. After firing the request, my backend successfully receives it but fails to recognize the custom headers provided. For example: $.ajax({ type: " ...

Incorporate text onto an image using Semantic UI

Currently, I am utilizing Semantic UI to display images. While it is functioning well for me in terms of adding text near the image, I desire to have the text positioned on top of the image. Ideally, I would like it to be located on the lower half of the i ...

Mongoose - facing issues with $and operator, looking for a way to locate an array containing both items

I'm currently developing a chat-based application that involves private messaging between individuals. Essentially, a Chat model in my application consists of the following schema: let schema = new Schema({ members: [{ type: ObjectId, ref: models.u ...

Incorporating SQLSRV results into clickable <td> elements: A dynamic approach

As a newcomer to the world of JS/PHP/web development, I'm seeking help with a seemingly simple task. My goal is to make each <td> element in a table clickable, and retrieve the text contained within the clicked <td>. Currently, I have a S ...

Using Typescript to import an npm package that lacks a definition file

I am facing an issue with an npm package (@salesforce/canvas-js-sdk) as it doesn't come with a Typescript definition file. Since I am using React, I have been using the "import from" syntax to bring in dependencies. Visual Studio is not happy about th ...

A guide on incorporating and utilizing third-party Cordova plugins in Ionic 5

Attempting to implement this plugin in my Ionic 5 application: https://www.npmjs.com/package/cordova-plugin-k-nfc-acr122u I have added the plugin using cordova plugin add cordova-plugin-k-nfc-acr122u but I am unsure of how to use it. The plugin declares: ...

What steps do I need to take to retrieve the passed slug in my API (slug=${params.slug})?

Is there a way for me to retrieve the passed slug in my API (slug=${params.slug}) while using the vercel AI SDK? const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat({ api: `/api/conversation?slug=${params.slug ...

The debounced function in a React component not triggering as expected

I am facing an issue with the following React component. Even though the raiseCriteriaChange method is being called, it seems that the line this.props.onCriteriaChange(this.state.criteria) is never reached. Do you have any insights into why this.props.onC ...

What is the best way to generate a live map with constantly updating markers?

Is it possible for me to learn how to develop a live map similar to the one on this site: www.lightningmaps.org? It's fascinating to watch new markers pop up every few seconds. I'm interested in building a real-time map that can track IP locatio ...

Transferring data between two functions within a React.js application

I have two functions called getLocation() and getLocationName(). The getLocation() function performs an XMLHttpRequest, and I want to pass the response to the getLocationName() function to display it in a list. getLocationName = (location) => { var ...

Utilizing AngularJS: Employing the $q Promise Feature to Await Data Readiness in this Scenario

I am currently facing an issue with my Controller and Factory. The Controller initiates an API call in the Factory, but I am struggling to make it wait for the data to be gathered before proceeding. This is where I believe implementing something like $q mi ...

Restart animation following a scroll occurrence

My experiment involves using the anime.js framework to create a simulation of leaves falling from a tree. The animation triggers when the user scrolls to the top of the page. However, I am facing an issue where the animation only plays once; if the user sc ...

Show a directional indicator on hover in the date selection tool

I am currently using a datepicker that looks like this: When I hover over it, three arrows appear to change days or show the calendar. However, I would like to remove these arrows. Here is the code snippet: link: function (scope, element, attr, ngModel ...

What are the steps for displaying multiple input fields using the onchange method?

$(document).on("change","#noofpack",function(){ count = $(this).val(); for(i=1;i<=count;i++){ $("#packageDiv").html('<input type="text" class="form-control" name="unit_price[]" placeholder="Unit Price" required="">'); ...

The length function appears to be signaling an unanticipated error

Recently, I encountered an issue with the code execution. Although the code appears to be functioning correctly, it is throwing an uncaught error. Is there a reason for concern regarding this situation? var xmlhttp = new XMLHttpRequest(); xmlhttp.onread ...

React is failing to display identical values for each item being mapped in the same sequence

I have implemented some standard mapping logic. {MEMBERSHIPS.map((mItem, index) => ( <TableCell className="text-uppercase text-center" colSpan={2} padding="dense" ...

Combining JWT authentication with access control lists: a comprehensive guide

I have successfully integrated passport with a JWT strategy, and it is functioning well. My jwt-protected routes are structured like this... app.get('/thingThatRequiresLogin/:id', passport.authenticate('jwt', { session: false }), thing ...