What's the most efficient method to recursively nest an array of objects into a tree structure with the id serving as a reference point?

My goal is to organize the pages in my database in a hierarchical "tree" view. Each page has a unique ID and a parent property that indicates which page it is a child of.

I am looking for an efficient way to recursively nest these objects so that each child page is nested within its appropriate parent page based on the parent ID reference. Here is the initial array:

[ 
    { 
        _id: 1,
        title: 'Page A-1',
        parent: null 
    },
    { 
        _id: 2,
        title: 'Page A-2',
        parent: 1 
    },
    { 
        _id: 3,
        title: 'Page A-3',
        parent: 2 
    },
    { 
        _id: 4,
        title: 'Page A-4',
        parent: 3 
    },
    { 
        _id: 5,
        title: 'Page A-5',
        parent: 4 
    },
    { 
        _id: 6,
        title: 'Page B-1',
        parent: null 
    },
    { 
        _id: 7,
        title: 'Page B-2',
        parent: 6 
    },
    { 
        _id: 8,
        title: 'Page B-3',
        parent: 7 
    },
    { 
        _id: 9,
        title: 'Page B-4',
        parent: 8 
    },
    { 
        _id: 10,
        title: 'Page B-5',
        parent: 8 
    },
    { 
        _id: 11,
        title: 'Page C-1',
        parent: null
    }
]

The desired transformed array would look like this:

[ 
    { 
        _id: 1,
        title: 'Page A-1',
        children: [
            { 
                _id: 2,
                title: 'Page A-2',
                children: [
                    { 
                        _id: 3,
                        title: 'Page A-3',
                        children: [
                            { 
                                _id: 4,
                                title: 'Page A-4',
                                children: [
                                    { 
                                        _id: 5,
                                        title: 'Page A-5',
                                        children: [

                                        ]
                                    }       
                                ]
                            }       
                        ]
                    }
                ]
            }       
        ]
    },
    { 
        _id: 6,
        title: 'Page B-1',
        children: [
            { 
                _id: 7,
                title: 'Page B-2',
                children: [
                    { 
                        _id: 8,
                        title: 'Page B-3',
                        children: [
                            { 
                                _id: 9,
                                title: 'Page B-4',
                                children: []
                            },
                            { 
                                _id: 10,
                                title: 'Page B-5',
                                children: []
                            }
                        ]
                    }       
                ]
            }
        ]
    },
    { 
        _id: 11,
        title: 'Page C-1',
        children: []
    }
]

Answer №1

Instead of focusing on the necessity for this task, I will share with you a highly efficient approach in terms of big-O complexity.

This algorithm consists of three steps with linear complexity O(items.length), making the entire process linear as well.

It's worth noting that in this tradeoff, I prioritize time complexity over space efficiency.

Step 1:

Start by iterating through your items and creating a Map<id, object>:

// You could also utilize a POJO here
// For demonstration purposes, I'm using a map as the data structure 
const map = items.reduce((map, item) => {
  map.set(item._id, item);
  // To avoid potential issues, consider not using original objects when building the tree
  // Instead, create new objects as shown in step 2 below
  /*
    map.set(item._id, {
      ...item,
      children: []
    })
  */
  return map;
}, new Map())

Step 2:

Proceed to iterate through the items and organize them under their respective parent:

items.forEach(item => {
  if (item.parent) {
    const parent = map.get(item.parent);
    // Modifying original objects can lead to bugs, so consider creating new objects in step 1
    if (!parent.children) {
      parent.children = [];
    }
    parent.children.push(item);
  }
})

Step 3:

Lastly, iterate through the items in the map and retrieve the top-level parents:

const result = [];
for (const item of map.values()) {
  if (!item.parent) {
    result.push(item);
  }
}

For your reference, here's a complete example:

const items=[{_id:1,title:"Page A-1",parent:null},{_id:2,title:"Page A-2",parent:1},{_id:3,title:"Page A-3",parent:2},{_id:4,title:"Page A-4",parent:3},{_id:5,title:"Page A-5",parent:4},{_id:6,title:"Page B-1",parent:null},{_id:7,title:"Page B-2",parent:6},{_id:8,title:"Page B-3",parent:7},{_id:9,title:"Page B-4",parent:8},{_id:10,title:"Page B-5",parent:8},{_id:11,title:"Page C-1",parent:null}];

// Step 1
const map = items.reduce((map, item) => {
  map.set(item._id, item);
  return map;
}, new Map())

// Step 2
items.forEach(item => {
  if (item.parent) {
    const parent = map.get(item.parent);
    if (!parent.children) {
      parent.children = [];
    }
    parent.children.push(item);
  }
})

// Step 3
const result = [];
for (const item of map.values()) {
  if (!item.parent) {
    result.push(item);
  }
}

console.log(result);

Keep in mind that while algorithmic complexity is crucial, actual implementation details and the efficiency of operations and data structures used also impact performance. For optimal results, benchmark testing may be necessary to fine-tune the algorithm.

Answer №2

While looping through the array, I recommend creating a lookup of all objects and assigning children to parents within the same loop. This method eliminates the need to search for parents in the tree, assuming the data size is manageable enough to avoid memory issues.

[Please note: The assumption here is that parents are defined before children in the array. If not, some additional work will be necessary within the loop.]

let info = [
    {
        id: 1,
        name: 'Item A-1',
        parent: null
    },
    {
        id: 2,
        name: 'Item A-2',
        parent: 1
    },
    {
        id: 3,
        name: 'Item A-3',
        parent: 2
    },

    {
        id: 4,
        name: 'Item A-4',
        parent: 3
    },
    {
        id: 5,
        name: 'Item A-5',
        parent: 4
    },
    {
        id: 6,
        name: 'Item B-1',
        parent: null
    },
    {
        id: 7,
        name: 'Item B-2',
        parent: 6
    },
    {
        id: 8,
        name: 'Item B-3',
        parent: 7
    },
    {
        id: 9,
        name: 'Item B-4',
        parent: 8
    },
    {
        id: 10,
        name: 'Item B-5',
        parent: 8
    },
    {
        id: 11,
        name: 'Item C-1',
        parent: null
    }
]

let index = {}
let structure = {}

info.forEach(element => {
    let itemObject = {id: element.id, name: element.name, children: []}

    if (!index[element.id]) index[element.id] = itemObject

    if (!element.parent) {
        structure[element.id] = itemObject
    } else {
        index[element.parent].children.push(itemObject)
    }
})
console.log(structure)

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

RegEx unable to extract a number ranging from 0 to 23

Can you figure out why this JavaScript regex is having trouble parsing a number between 0 and 23? pattern = /([0-1]?[0-9]|2[0-3])/ "12".match(pattern) // => matches 12 "23".match(pattern) // => matches 2 (should match 23) ...

Updating the database with values dynamically using ajax without the need to refresh or change the current page

My current challenge involves adding records to a database table without the need to reload the page. I've implemented ajax for this purpose, but have been receiving an unexpected response (201) from the server (alert("Error occurred !")). Despite spe ...

"Limitation observed - function operates only on first attempt

I have been working on a map project that involves pulling information from Google about various locations using the library available at https://github.com/peledies/google-places In order to troubleshoot and identify the issue with the code related to ma ...

Tips for testing a mapbox popup using jasmine testing?

I encountered an issue with my mapbox popup while using jasmine and attempting to write a unit test for it. Here is the function in question: selectCluster(event: MouseEvent, feature: any) { event.stopPropagation(); this.selectedCluster = {geo ...

When I request the value of a JavaScript object, I am met with 'undefined'

I have been working on creating a Google map application that involves loading markers from a database and displaying them on a map. To accomplish this, I decided to create an array and define an object as shown below: function shop_info(name, latitude, l ...

How to export multiple dictionaries into a single CSV file with a shared header using Python

Trying to write multiple dictionaries to a csv file can be a bit challenging. I recently managed to achieve this for two dictionaries, but what if there are multiple dictionaries that need to be written? My current project involves streaming tweets and co ...

"Trouble arose when I tried to incorporate additional functions into my JavaScript code; my prompt feature is not

As a beginner in HTML and JS coding, I am working on creating a page that prompts user input for a name and then executes animations using radio buttons. However, after adding functions for radio button changes, my prompt is no longer functioning properly. ...

Troubleshooting issue with changing class based on input values

It appears that there is an issue with the functionality when switching values on page load. Initially, I was able to make it work for a single switch, but now that there are multiple switches on the page, toggling affects all of them. How can I modify it ...

I need assistance from someone knowledgeable in HTML and CSS. I am trying to create a div that dynamically increases its width until it reaches a specific

Seeking assistance to create a dynamic div that continuously expands its width until it reaches a maximum of 540px. It should start at 75px. Below is the HTML and CSS code I've attempted: .Loading-Screen { background-color: black; color: alicebl ...

What steps can be taken to repair the script?

https://jsfiddle.net/fnethLxm/10/ $(document).ready(function() { parallaxAuto() }); function parallaxAuto() { var viewer = document.querySelector('.viewer.active'), frame_count = 5, offset_value = 500; // init control ...

Overlapping agendas within Microsoft Planner

A request needs to be made to POST v1.0/groups with the following JSON body: { "description": "hello", "displayName": "group_for_restore", "groupTypes": [ "Unified" ], "mailEnabled": true, "mailNickname": "group_for_re ...

Converting the given data into an object in JavaScript: a step-by-step guide

"[{'id': 3, 'Name': 'ABC', 'price': [955, 1032, 998, 941, 915, 952, 899]}, {'id': 4, 'Name': 'XYZ', 'id': [1016, 1015, 1014, 915, 1023, 1012, 998, 907, 952, 945, 1013, 105 ...

The ng-disabled directive in AngularJS fails to disable the button as expected

I'm having an issue with setting an input button to disabled when a user selects a specific value from a select box: Here is my select menu: <select id="jobstatus" name="jobstatus" ng-model="associate.JobStatus" class="form-control"> & ...

Unable to install vue-property-decorator

When attempting to set up Vue and TypeScript with class style using vue-property-decorator, I encountered a strange script after creating the project. I was anticipating a script like this: <script lang="ts"> import {Component, Vue} from & ...

Display the name of the file on the screen

Is there a way to dynamically display the file name in a view instead of hardcoding it? I would appreciate any assistance. Thank you! Here is my code snippet: <li> @if (Model.Picture2 != null) { base2 = Convert.ToBase64String(Model.Pict ...

PHP REST API to handle requests and generate corresponding responses

I am currently struggling to find a solution for accurately translating the responses and requests for my PHP REST API. Here is an example of an array that I receive from the API: [{ "id": "49557a36-028b-40c6-b2d8-8095468af130", "startDate": "2020-04- ...

Creating dynamic qtip2 tooltips is an effective way to enhance user interaction on

I am facing an issue where all tooltips work perfectly fine when the page loads, but stop working after data refreshes through ajax. How can I resolve this problem? $(document).ready(function() { // MAKE SURE YOUR SELECTOR MATCHES SOMETHING IN YOUR HT ...

Unraveling Vue Async Components - Harnessing the power of emitted events to resolve

I am looking to create a Vue async component that stays in a loading state until a custom event is triggered. This means it will render a specified loading component until the event occurs. Here's an example of how I want it to work: const AsyncComp ...

What is the most effective way to transfer an array from one PHP file to a different JavaScript file?

Utilizing AJAX to send a request to another php page and retrieve the result of a query, I originally used xmlhttprequest to pass the php logic along with the query information. However, I wanted to separate the presentation logic from the actual code logi ...

Vue JS: dynamic reactive structure

As I delved into understanding the basics of reactivity and its syntax, I encountered an interesting problem. Take a look at this code snippet: const app = Vue.createApp({ data: dataFunction, methods: {method1: methodImpl} }) var dataObj = { tit ...