In JavaScript, efficiently remove specific node types from a tree structure using recursion, while also maintaining and distributing qualified child nodes

Currently, I am working on a recursive function that operates on a JSON tree structure {name, type, [children]}, with the goal of removing nodes of a specific type. However, it is essential that the children of the removed node are reattached to the parent if they do not match the type being removed.

I am encountering a challenge in my implementation. Let's consider the scenario where I want to eliminate nodes of type "b" from the following tree:

const exampleData = [{
name: "parent",
type: "a",
children: [{
    name: "childA",
    type: "a",
    children: null
    },{
    name: "childB",
    type: "b",
    children: [{
        name: "grandChildA",
        type: "a",
        children: null
        },{
        name: "grandChildB",
        type: "a",
        children: null
        }]
    },{
    name: "childC",
    type: "a",
    children: null
    }]
}]

The original set of children for the parent node includes [childA, childB, childC]. Following the removal operation, we expect the parent to have children as

[childA, grandChildA, grandChildB, childC]
. However, the current outcome is
[childA, [grandChildA, grandChildB], childC]
.

I acknowledge the need to use the spread operator but I am uncertain about its placement within the recursion logic.

Presented below is the current version of my function (acknowledging that the usage of the spread syntax may be incorrect):

const removeType = (node, type) => {
    // Handling the case where the node should not be removed    
    if (node.type !== type){
        // If the node possesses children, call the function recursively to prune them
        if (node.children && node.children.length > 0){
            node.children = [...node.children.map(child => removeType(child, type))
                                             .filter(child => child !== null)]
            return node
        }
        // If the node has no children, simply return the node itself
        else return node
    }
    // Handling the scenario where the node needs to be removed
    else if (node.type === type){
        // In cases where the node contains children, make a recursive call and then reattach the children
        if (node.children && node.children.length > 0){
            node.children = [...node.children.map(child => removeType(child, type))
                                             .filter(child => child !== null)]
            return node.children
        }
        // Otherwise, return null
        else return null
    }
}

Answer №1

Revised Information

One approach could be to utilize the reduce method for this task. While I don't have access to my computer at this moment to confirm, the structure may resemble the following:

const removeType = (node, type) => {
   if (node === null) {
     return null;
   } else {
    return node.reduce((acc, child) => {
      if(child["type"] === type) {
        const removedChild = removeType(child["children"], type);
        acc = [...acc, ...removedChild];
      } else {
        child.children = removeType(child["children"], type);
        acc.push(child);
      }
      return acc;
    }, []);
  }
}

Latest Update

Streamlined code version:

const removeType = (node, type) => {
    if (!node) return;

    return node.reduce((acc, child) => {
        if(child["type"] === type) {
            const removedChild = removeType(child["children"], type);
            acc = [...acc, ...removedChild];
        } else {
            child.children = removeType(child["children"], type);
            acc.push(child);
        }
        return acc;
    }, []);

}

Answer №2

This particular answer offers a unique perspective compared to the other solutions provided. It follows a recursive structure similar to the one presented by Thankyou, with the added assumption that the input is always an array and all non-nil children nodes are arrays as well.

const filterType = (node, target) =>
  node .flatMap (({type, children, ...rest}) =>
    type === target
      ? children ? filterType (children, target) : []
      : [{...rest, type, children: children && (filterType (children, target))}]
  )

const dataExample = [{name: "parent", type: "a", children: [{name: "childA", type: "a", children: null},{name: "childB", type: "b", children: [{name: "grandChildA", type: "a", children: null},{name: "grandChildB", type: "a", children: null}]}, {name: "childC", type: "a", children: null}]}]

console .log (
 filterType (dataExample, 'b')
)
.as-console-wrapper {min-height: 100% !important; top: 0}

This approach may not necessarily be an enhancement over existing solutions, but it presents an intriguing alternative method.

Answer №3

Here's a program simplification technique involving the use of Array.prototype.flatMap, mathematical induction, and mutual recursion:

  • removeType function accepts an array of nodes and a query type to remove, q
  • removeType1 function accepts a single node and a query type to remove, q
const removeType = (nodes, q) =>
  (nodes || []).flatMap(n => removeType1(n, q))

const removeType1 = (node, q) =>
  q === node.type
    ? removeType(node.children)
    : { ...node, children: removeType(node.children, q) }

const input = 
  [{name:"parent",type:"a",children:[{name:"childA",type:"a",children:null},{name:"childB",type:"b",children:[{name:"grandChildA",type:"a",children:null},{name:"grandChildB",type:"a",children:null}]},{name:"childC",type:"a",children:null}]}]
  
const result =
  removeType(input, "b")

console.log(result)

Output -

[
  {
    "name": "parent",
    "type": "a",
    "children": [
      {
        "name": "childA",
        "type": "a",
        "children": []
      },
      {
        "name": "grandChildA",
        "type": "a",
        "children": []
      },
      {
        "name": "grandChildB",
        "type": "a",
        "children": []
      },
      {
        "name": "childC",
        "type": "a",
        "children": []
      }
    ]
  }
]

Note that the result is a new object and the original input remains unchanged.


In the above problem, mutual recursion works well. But what if you only want one function?

const removeType = (t, q) =>
  Array.isArray(t)                             
    ? t.flatMap(node => removeType(node, q))
: Object(t) === t                              
    ? q === t.type
        ? removeType(t.children, q)
        : { ...t, children: removeType(t.children, q) }
: t                                            

const input = 
  [{name:"parent",type:"a",children:[{name:"childA",type:"a",children:null},{name:"childB",type:"b",children:[{name:"grandChildA",type:"a",children:null},{name:"grandChildB",type:"a",children:null}]},{name:"childC",type:"a",children:null}]}]
  
const result =
  removeType(input, "b")

console.log(result)

The output is almost the same, but notice how this version retains children: null. The original implementation gives the more accurate children: [].

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

Configuring Webpack for a React application with Express and Babel

I am in the process of developing a React application, utilizing React version ^15.6.1 along with Webpack, Express, Babel, and Yarn as the package manager. After successfully running the application, I am now preparing to transition it to production by co ...

Steps for adjusting the status of an interface key to required or optional in a dynamic manner

Imagine a scenario where there is a predefined type: interface Test { foo: number; bar?: { name: string; }; } const obj: Test; // The property 'bar' in the object 'obj' is currently optional Now consider a situatio ...

Adjust the size of an image post-render with the help of Angular or jQuery

Below is my current HTML code: <img width="150" height="150" src="https://d.pcd/image/578434201?hei=75&amp;wid=75&amp;qlt=50,0&amp;op_sharpen=1&amp;op_usm=0.9,0.5,0,0" ng-src="https://d.pcd/image/578434201?hei=75&amp;wid=75&amp; ...

How do webpack imports behave within a ReactJS project?

In my ReactJS project, I have utilized various 3rd party libraries such as lodash, d3, and more. Interestingly, I recently discovered that I did not explicitly write imports like import d3 from 'd3' or import _ from 'lodash' in all of ...

Node.js is throwing an error "Unexpected token v in JSON at position 0" which

I'm currently working on developing PHP code within the Google App Engine flexible environment. Specifically, I am facing some challenges in setting up the Firebase SDK Admin for the web version. My main struggle lies with the following block of code ...

A variety of negative () DOM Selectors

I've been trying to select a specific node using two not clauses, but so far I haven't had any luck. What I'm attempting to achieve is selecting an element whose div contains the string 0008, but it's not 10008 and also does not contain ...

What is the best way to remove the left margin from a MuiTreeItem-group?

I'm working with the MUI tree component and I want to remove the left margin of the second layer of MuiTreeItem-group I attempted to use makeStyles to address this issue, but it didn't have the desired effect const useStyles = makeStyles(() => ...

ElegantTree - Efficient method for populating data-content in a PHP-generated directory

Embarking on the journey of programming and FancyTree, I find myself struggling with English ^^. A little guidance wouldn't hurt, thank you. I've successfully generated a PHP tree structure for my files and folders to use with FancyTree. So far, ...

Error message stating: "jsp encountered a java.lang.NullPointerException"

Hey everyone, I'm new to Java and JSP and I don't have much knowledge about JSON. However, I need to pull data from a database and display it on a JSP page using Bootstrap Data Table. I chose this because it offers features like Pagination, Filte ...

Is it possible to utilize an XML format for translation files instead of JSON in React Native?

I'm in the process of creating a react native application using the react i18next library. For translations, I've utilized XML format in android for native development. In react native, is it possible to use XML format for translation files inste ...

Execute and showcase code without redundancies

I have been exploring a way to store JavaScript code within an object and execute specific parts of it upon the user clicking a button. Here's what I've come up with so far: var exampleCode = { test: "$('body').css('background ...

Is it possible to display the command output in Grunt as it runs?

When I run this command, I am not seeing any output and it runs endlessly: grunt.registerTask('serve', function () { var done = this.async(); grunt.util.spawn({ cmd: 'jekyll', args: ['serve'], ...

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 ...

commenting system through ajax across multiple web pages

Experimenting with the webcodo comment system has led me to this URL: Database table: "comments" CREATE TABLE IF NOT EXISTS `comments` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(40) NOT NULL, `email` varchar(60) NOT NULL, `comment` te ...

Techniques for eliminating text enclosed by double parentheses in javascript

I am currently working on extracting data from Wikipedia, but I am facing a challenge with removing content enclosed in multiple parentheses. While I can successfully remove single parentheses using content.replace(/\s*\(.*?\)\s*/g, &ap ...

Text displayed in dropdown when selecting autocomplete option from Material UI

I'm facing a problem with the Material UI's Autocomplete Component. The issue is that the value is being shown instead of the text element. My data source format looks like this: [{value: 'someValue', text: 'My Text'}, {value ...

The error message "Seed is not defined" is raised when the program attempts to

I'm currently diving into fullstack vue and I'm perplexed by the error occurring in this particular scenario. window.Seed = (function () { const submissions = [ { id: 1, title: 'Yellow Pail', ...

What is preventing me from injecting a service into an Angular factory/injector?

I came up with an authentication service (AuthenticationService) that I utilize for making calls to a WEB API. Recently, I stumbled upon interceptors and got intrigued. I thought it would be interesting to create an interceptor that can intercept requests ...

Building a nested dictionary with keypath in Python

A complex nested dictionary contains multiple levels of keys. The task at hand involves: Creating nested keys using keypaths if they don't already exist Updating values using keypaths if they do exist Consider the following example dictionary: { ...

Utilizing the split function within an ngIf statement in Angular

<div *ngIf="store[obj?.FundCode + obj?.PayWith].status == 'fail'">test</div> The method above is being utilized to combine two strings in order to map an array. It functions correctly, however, when attempting to incorporate the spli ...