Generating a multi-dimensional array from a one-dimensional array - Structuring Data

Looking to create a nested array utilizing the path as a guide for child elements. For example, 4.1 is a child of 4, 4.1.1 is a child of 4.1, and so on. Given a flat array with all data and paths, what's the optimal method to construct a nested array where children are appropriately nested under their parent based on the path?

Input:

const list = [
  {
    location: 1,
    path: '4'
  },
  {
    location: 2,
    path: '4.1'
  },  
  {
    location: 3,
    path: '4.1.1'
  },  
  {
    location: 4,
    path: '4.1.2'
  },  
  {
    location: 5,
    path: '4.2'
  },  
  {
    location: 6,
    path: '4.2.1'
  },
  {
    location: 7,
    path: '4.3'
  },
  {
    location: 8,
    path: '4.3.1'
  }
];

Output:

const  list = [
  {
    location: 1,
    path: '4',
        children: [
            {
                location: 2,
                path: '4.1',
                children: [
                    {
                        location: 3,
                        path: '4.1.1'
                    },  
                    {
                        location: 4,
                        path: '4.1.2'
                    },  
                 ]
             },  
             {
                 location: 5,
                 path: '4.2',
                 children: [
                     {
                         location: 6,
                         path: '4.2.1'
                     },
                 ]
             },  
             {
                 location: 7,
                 path: '4.3',
                 children: [
                     {
                         location: 8,
                         path: '4.3.1'
                     }
                 ]
             },
         ]
   },
];

An ideal solution would involve recursion. Any thoughts on how best to approach this algorithm?

Answer №1

After exploring Scott's linked answer, I was pleased to find that it effectively resolved the issue at hand.

import { tree } from './Tree'
import { bind } from './Func'

const parent = (path = "") =>
  bind
    ( (pos = path.lastIndexOf(".")) =>
        pos === -1
          ? null
          : path.substr(0, pos)
    )

const myTree =
  tree                          // <- make tree
    ( list                      // <- array of nodes
    , node => parent(node.path) // <- foreign key
    , (node, children) =>       // <- node reconstructor
        ({ ...node, children: children(node.path) }) // <- primary key
    )

console.log(JSON.stringify(myTree, null, 2))
[
  {
    "location": 1,
    "path": "4",
    "children": [
      {
        "location": 2,
        "path": "4.1",
        "children": [
          {
            "location": 3,
            "path": "4.1.1",
            "children": []
          },
          {
            "location": 4,
            "path": "4.1.2",
            "children": []
          }
        ]
      },
      {
        "location": 5,
        "path": "4.2",
        "children": [
          {
            "location": 6,
            "path": "4.2.1",
            "children": []
          }
        ]
      },
      {
        "location": 7,
        "path": "4.3",
        "children": [
          {
            "location": 8,
            "path": "4.3.1",
            "children": []
          }
        ]
      }
    ]
  }
]

The Tree logic can be found in this helpful post, and below is a snippet from the Func module containing the essential bind function:

// Func.js
const identity = x => x

const bind = (f, ...args) =>
  f(...args)

const raise = (msg = "") => // functional throw
  { throw Error(msg) }

// ...

export { identity, bind, raise, ... }

To confirm the outcomes, expand the code block below in your browser:

// Func.js
const bind = (f, ...args) =>
  f(...args)
  
// Index.js
const empty = _ =>
  new Map

const update = (r, k, t) =>
  r.set(k, t(r.get(k)))

const append = (r, k, v) =>
  update(r, k, (all = []) => [...all, v])

const index = (all = [], indexer) =>
  all.reduce
      ( (r, v) => append(r, indexer(v), v)
      , empty()
      )
      
// Tree.js
// import { index } from './Index'
function tree (all, indexer, maker, root = null)
{ const cache =
    index(all, indexer)

  const many = (all = []) =>
    all.map(x => one(x))
    
  const one = (single) =>
    maker(single, next => many(cache.get(next)))

  return many(cache.get(root))
}

// Main.js
// import { tree } from './Tree'
// import { bind } from './Func'

const parent = (path = "") =>
  bind
    ( (pos = path.lastIndexOf(".")) =>
        pos === -1
          ? null
          : path.substr(0, pos)
    )

const list =
  [{location:1,path:'4'},{location:2,path:'4.1'},{location:3,path:'4.1.1'},{location:4,path:'4.1.2'},{location:5,path:'4.2'},{location:6,path:'4.2.1'},{location:7,path:'4.3'},{location:8,path:'4.3.1'}]

const myTree =
  tree
    ( list                      // <- array of nodes
    , node => parent(node.path) // <- foreign key
    , (node, children) =>       // <- node reconstructor
        ({ ...node, children: children(node.path) }) // <- primary key
    )

console.log(JSON.stringify(myTree, null, 2))

Answer №2

To achieve this task, one approach is to utilize an intermediate index that maps paths to objects. By folding the list into a structured format, we can retrieve each node and its parent from the index. If a parent is not found, we add it to the root object. Eventually, we return the children of the root object. Below is the implementation:

const restructure = (list) => {
  const index = list.reduce(
    (a, {path, ...rest}) => ({...a, [path]: {path, ...rest}}), 
    {}
  )
  
  return list.reduce((root, {path}) => {
    const node = index[path]
    const parent = index[path.split('.').slice(0, -1).join('.')] || root
    parent.children = [...(parent.children || []), node]
    return root
  }, {children: []}).children
}

const list = [{location: 1, path: '4'}, {location: 2, path: '4.1' }, {location: 3, path: '4.1.1'}, {location: 4, path: '4.1.2'}, {location: 5, path: '4.2'}, {location: 6, path: '4.2.1'}, {location: 7, path: '4.3'}, {location: 8, path: '4.3.1'}]

console.log(restructure(list))
.as-console-wrapper {min-height: 100% !important; top: 0}

Utilizing the index eliminates the need for sorting as the input order does not matter.

Determining the parent involves substituting, for example, "4.3.1" with "4.3" and searching for it in the index. When attempting "4", it refers to the empty string, fails to locate it, and resorts to the root node.

If you prefer using regex, you can opt for this shorter line instead:

const parent = index[path.replace(/(^|\\.)[^.]+$/, '')] || root

Alternatively, you may want to explore a more elegant solution mentioned in a recent answer to a similar query. Although my approach achieves the desired outcome (despite some messy mutations), the suggested answer provides valuable insights into efficient software development practices.

Answer №3

To begin, arrange the array of objects by path to ensure that the parent precedes its children in the sorted array. For example, '4' should come before '4.1.'

Next, establish an object where the keys represent the paths. Let's assume '4' has already been added to our object.

obj = {
  '4': {
    "location": 1,
    "path": "4",    
  }
}

When handling '4.1', start by checking if '4' exists in our object. If it does, navigate to its children (if 'children' key is absent, create a new empty object) and verify if '4.1' is present. If not, add '4.1'.

obj = {
  '4': {
    "location": 1,
    "path": "4",
    "children": {
      "4.1": {
        "location": 2,
        "path": "4.1"
      }
    }
  }
}

Repeat this process for each element in the list. Finally, recursively convert this object into an array of objects.

Final implementation:

list.sort(function(a, b) {
  return a.path - b.path;
})

let obj = {}

list.forEach(x => {
  let cur = obj;
  for (let i = 0; i < x.path.length; i += 2) {
    console.log(x.path.substring(0, i + 1))
    if (x.path.substring(0, i + 1) in cur) {
      cur = cur[x.path.substring(0, i + 1)]
      if (!('children' in cur)) {
        cur['children'] = {}
      }
      cur = cur['children']
    } else {
      break;
    }
  }
  cur[x.path] = x;
})

function recurse (obj) {
  let res = [];
  Object.keys(obj).forEach((key) => {
    if (obj[key]['children'] !== null && typeof obj[key]['children'] === 'object') {
      obj[key]['children'] = recurse(obj[key]['children'])
    }
    res.push(obj[key])
  })
  return res;
}

console.log(recurse(obj));

Answer №4

My approach was similar to Aadith's, but I took an iterative route. In my opinion, the most efficient method would be to utilize a linked list structure and then flatten it.

const list = [
  {
    location: 1,
    path: '4'
  },
  {
    location: 2,
    path: '4.1'
  },  
  {
    location: 3,
    path: '4.1.1'
  },  
  {
    location: 4,
    path: '4.1.2'
  },  
  {
    location: 5,
    path: '4.2'
  },  
  {
    location: 6,
    path: '4.2.1'
  },
  {
    location: 7,
    path: '4.3'
  },
  {
    location: 8,
    path: '4.3.1'
  }
];

let newList = [];
list.forEach((location) => {
  console.log('Handling location ', location);
  if(location.path.split('.').length == 1)
  {
    location.children = [];
    newList.push(location);
  }
  else
  {
    newList.forEach(loc => {
      console.log('checking out: ', loc);
      let found = false;
      while (!found)
      {
        console.log(loc.path, '==', location.path.substring(0, location.path.lastIndexOf('.')));
        found = loc.path == location.path.substring(0, location.path.lastIndexOf('.'));
        if (!found)
        {
          for (let i = 0; i < loc.children.length; i++)
          {
            let aloc = loc.children[i];
            
            found = aloc.path == location.path.substring(0, location.path.lastIndexOf('.'));
            if (found)
            {
              console.log('found it...', loc);
              location.children = [];
              aloc.children.push(location);
              break;
            }
          }
        }
        else
        {
          console.log('found it...', loc);
          location.children = [];
          loc.children.push(location);
        }
      }
    });
  }
});

console.log(newList);

This was my unique way of tackling the problem at hand.

Answer №5

Appreciation for the plethora of suggestions provided! I was able to envision some truly advanced solutions to tackle my issue. However, at the end of the day, I opted for a more straightforward "dirty" solution.

While it may not be the fastest approach, considering the length of the list in my application, optimization concerns are minimal for now.

I simplified the flattened list for the sake of my query, but in reality, the list was slightly more intricate than portrayed. Additionally, passing the desired path to locate its children was also feasible.

The following solution proved effective for me:

function handleNested(list, location) {
  return list.map((item) => {
    if (item.children && item.children.length > 0) {
      item.children = handleNested(item.children, location);
    }
    if (
      location.path.startsWith(item.path) &&
      location.path.split(".").length === item.path.split(".").length + 1
    ) {
      return {
        ...item,
        children: item.children ? [...item.children, location] : [location],
      };
    } else {
      return item;
    }
  });
}

function locationList(path) {
  // Filtering the list to remove items with different parent path, and sorting by path depth.
  const filteredList = list
    .filter((location) => location.path.startsWith(path))
    .sort((a, b) => a.path.length - b.path.length);

  let nestedList = [];
  // Looping through the filtered list and organizing each item under its parent.
  for (let i = 0; i < filteredList.length; i++) {
    // Utilizing a recursive function to determine its parent.
    let res = handleNested(nestedList, filteredList[i]);
    nestedList = res;
  }

  return nestedList;
}

locationList("4");

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

What is the method for displaying the value of a textarea?

I am relatively new to the world of coding, but I have already delved into HTML, CSS, and basic DOM scripting. My goal is simple - I want to create a comment box where people can leave messages like in a guestbook. However, when I input text and click on ...

Vue for Number Crunching

Learning vueJS is quite new to me. I am attempting to capture two input values, add them together, and display the result. I have encountered a strange issue where when subtracting number1 from number3, multiplying number1 with number2, or dividing number ...

Exploring the different routing options for Nuxt.js: Dynamic versus Basic Routing

Can anyone suggest the best way to set up routing in Next.js for only Home, Gallery, and Contact us? Should I use dynamic routing or just keep it basic? Any ideas on how to path them? I'm still learning, so I would appreciate some guidance. I've ...

Imagine a complex JSON structure with multiple levels of nesting

Take a look at this JSON data : { department_1 : [{ id : 1, name = Joe Smith, email : <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="660c150b0f120e2613150048030213">[email protected]</a>}, ...., { id : 500, name ...

"Resizing a Javascript file resulting in the creation of a faulty base64

I have been attempting to resize images on the client side using HTML5 before uploading. However, it seems that the base64 string is broken as it contains spaces, line breaks, and other unexpected characters. Even after removing them, the string remains br ...

Retrieving information from the database and mapping it to an array

Currently in my Angular application, I have hardcoded values in an array and bound them to a dropdown list using the in-built control, which is functioning correctly. The current setup looks like this: export class UserDetailsComponent implements OnInit { ...

Empty Array returned in VueJS after Axios GET request | VUEJS/LARAVEL

Currently, I am working on a project for my university while also learning how to build a single page application through a Udemy course. The issue I'm facing is related to pushing data from a JSON database query named "alunos" to the front end using ...

Can you help me locate the most expansive free area in an array using C programming?

Having an array of characters that can be represented as: 1 0 0 1 1 1 0 0 0 1 1 0 1 The challenge at hand is to create a search algorithm for identifying the ideal location in the array. Linear search is not allowed, leading to the need for an alte ...

Activate the following and preceding elements within the 'vue-owl-carousel' component for Vue.js

I'm facing an issue with navigating to the next or previous item in the vue-owl-carousel package while using Vue and javascript. Is there anyone who can provide assistance? Below is the code for the carousel : <carousel id="newGamesCarousel" :it ...

Updating State and Modifying URL in ReactJS when Input Changes

I am currently attempting to modify the state and URL onChange within a <Input type='select'> element, utilizing reactstrap. import React, {Component} from 'react' import { Input, } from 'reactstrap' export default ...

Issues persist with $.getJSON

I apologize if this question has been addressed previously, but I couldn't find the answer I was seeking. My issue involves using $.getJSON to send variables to a PHP file. Although the file returns success as true, it always triggers the .fail functi ...

The bidirectional data binding feature is malfunctioning following an XMLHttpRequest request

When watching a property from Vuex, I use the following approach: computed: { ticket() { return this.$store.getters['order/reservation'].offer_id } }, watch: { ticket(newTicketId) { console.log('Caught change of ...

Transfer and transform numerous arrays between Android and PHP server

Being new to Android, I am struggling with converting this array into a JSON array and sending it to the PHP server: {"menu":[{"id":"2","number":"3"}, {"id":"6","number":"4"}, {"id":"5","number":"6"}], "user":[{"uid": "12345","number":"<a href="/cdn- ...

Choose all from the list of categories

function CheckItems(chk) { if(document.myform.brandid.value!="Check all"){ for (i = 0; i < chk.length; i++) chk[i].checked = true ; document.myform.brandid.value="UnCheck all"; }else{ for ( ...

The functionality of Webdriver.waitUntil is not meeting the expected outcomes

I'm currently utilizing webdriverio version 4.5: ./node_modules/.bin/wdio -v v4.5.2 In my scenario, I am in need of waiting for the existence of a specific element and handling the situation if it doesn't exist. Here is an example code snippet ...

Error: Python data loading can only convert arrays with a length of 1 to scalars in a dataset

Apologies for my lack of experience within the community. This question may seem simple, but I am struggling with it. I have generated a numpy matrix and now I want to evaluate the density points using the meanshift algorithm. However, I'm encounterin ...

Having trouble getting the Facebook like button to display on my website using an iframe in the markup

I gave it my all to try and get it to work, but unfortunately, I was unsuccessful. This is the approach I took. First, I followed the instructions provided on https://developers.facebook.com/docs/plugins/like-button. Next, I copied and pasted the iframe ...

Error encountered when assigning a value to a session array in PHP 7.0

I am the author of this script <?php session_start(); if($_POST["num"] > $_SESSION["ratings"][$_POST["title"]]) $SESSION["ratings"][$_POST["title"]] = $_POST["num"]; ?> Although my syntax seems correct to me, the IDE is showin ...

Employing ajax with dynamically created buttons in PHP

I'm struggling to figure out what to search for in this situation. I've tried piecing together code from others, but it's just not working for me. My ajax function successfully retrieves data from a database through a php page and displays ...

Tips on selecting specific data from a nested JSON array based on conditions and fetching just one value from the initial filtered result with Javascript (designed for Google Sheets)

Utilizing the TMDB API for retrieving movie data and integrating it into a Google Sheet. The original Google Sheet was revamped from Reddit user 6745408's "MediaSheet 3.0". This sheet incorporates a Javascript-based script. By following the patterns/c ...