0

I have javascript array, where each item has reference to parent, and they can be looped (circular reference). Example:

[
        {"id": 1, "firstName": "Macko","parentId": 12},
        {"id": 2, "firstName": "Jess","parentId": 1},
        {"id": 3, "firstName": "Peter","parentId": 1},
        {"id": 4, "firstName": "Lisa", "parentId": 1},
        {"id": 5, "firstName": "Megan","parentId": 1},
        {"id": 6, "firstName": "John", "parentId": 4},
        {"id": 7, "firstName": "Joe", "parentId": 4},
        {"id": 8, "firstName": "Matthew","parentId": 2},
        {"id": 9, "firstName": "Peter","parentId": 2},
        {"id": 10, "firstName": "Dio","parentId": 5},
        {"id": 11, "firstName": "Hello","parentId": 5},
        {"id": 12, "firstName": "Ana", "parentId": 4}
]

I needed to create nested data structure based on selected record to display it in the DOM, which I achieved by recursive function like below (source here)

function getNestedChildren(arr, parent) {
  var out = []
  for(var i in arr) {
    if(arr[i].parent == parent) {
        var children = getNestedChildren(arr, arr[i].id)

        if(children.length) {
            arr[i].children = children
        }
        out.push(arr[i])
    }
  }
  return out
}

It works really well, but not for circular data structures. The thing is I need to stop function execution before it reaches to element from which it started.

How can I achieve this?

omarjmh
  • 13,632
  • 6
  • 34
  • 42
Ketus
  • 123
  • 1
  • 11
  • add an argument (e.g. array) that keeps a list of visited IDs so you can check it and stop when appropriate. – Cyb3rFly3r Apr 16 '16 at 16:06
  • @Cyb3rFly3r I tried that but got wierd or incomplete results. Could you post an example? I propably put condition in wrong place. – Ketus Apr 16 '16 at 16:08
  • It's unclear what your intended result is since the original code always produces an empty array. – HeadCode Apr 16 '16 at 17:10

3 Answers3

1

Perhaps something like this should work for you:

function getNestedChildren(arr, parent, visited_list) {
  var out = []
  for(var i in arr) {
    if(!(arr[i].id in visited_list) && (arr[i].parentId == parent)) {

        visited_list[arr[i].id] = true;
        var children = getNestedChildren(arr, arr[i].id, visited_list)

        if(children.length) {
            arr[i].children = children
        }
        out.push(arr[i])
    }
  }
  return out
}

nestedList = getNestedChildren(arr, 1, [])
Cyb3rFly3r
  • 1,321
  • 7
  • 12
  • hm i thought it would work but when i start from ID 12, 4 levels deep there's also object with ID 12. Any ideas? – Ketus Apr 16 '16 at 17:47
1

You could mark the entries that you have already visited. Based on that you can skip processing the same element twice.

As you are adding the children property to the elements, you could use that for this marking purpose, provided you also create this property when an element has no children.

Here is working code doing this:

function getNestedChildren(arr, parent) {
  var out = [];
  for(var i in arr) {
    if(arr[i].parentId == parent) {
        if (arr[i].children === undefined) {
            arr[i].children = []
            var children = getNestedChildren(arr, arr[i].id)
            arr[i].children = children
        }
        out.push(arr[i])
    }
  }
  return out
}

var arr = [
    {"id": 1, "firstName": "Macko","parentId": 12},
    {"id": 2, "firstName": "Jess","parentId": 1},
    {"id": 3, "firstName": "Peter","parentId": 1},
    {"id": 4, "firstName": "Lisa", "parentId": 1},
    {"id": 5, "firstName": "Megan","parentId": 1},
    {"id": 6, "firstName": "John", "parentId": 4},
    {"id": 7, "firstName": "Joe", "parentId": 4},
    {"id": 8, "firstName": "Matthew","parentId": 2},
    {"id": 9, "firstName": "Peter","parentId": 2},
    {"id": 10, "firstName": "Dio","parentId": 5},
    {"id": 11, "firstName": "Hello","parentId": 5},
    {"id": 12, "firstName": "Ana", "parentId": 4}
]

getNestedChildren(arr, 1)

// Output the lengths of the children's arrays
document.body.innerHTML = arr.map(function (item) {
    return 'Item ' + item.id + ' has ' + item.children.length + ' children.'
}).join('<br>')
trincot
  • 317,000
  • 35
  • 244
  • 286
1

The checked array keeps the ids of all the objects (parents) getNestedChildren was already called upon.

If the current child's id is in that array, do not include it as a child.

var arr = [
  {"id": 1, "firstName": "Macko","parentId": 12},
  {"id": 2, "firstName": "Jess","parentId": 1},
  {"id": 3, "firstName": "Peter","parentId": 1},
  {"id": 4, "firstName": "Lisa", "parentId": 1},
  {"id": 5, "firstName": "Megan","parentId": 1},
  {"id": 6, "firstName": "John", "parentId": 4},
  {"id": 7, "firstName": "Joe", "parentId": 4},
  {"id": 8, "firstName": "Matthew","parentId": 2},
  {"id": 9, "firstName": "Peter","parentId": 2},
  {"id": 10, "firstName": "Dio","parentId": 5},
  {"id": 11, "firstName": "Hello","parentId": 5},
  {"id": 12, "firstName": "Ana", "parentId": 4}
];

var getNestedChildren = function(arr, id, checked) {

  var out = [];
  for (var i = 0; i < arr.length; i++) {
    if (arr[i].parentId === id && checked.indexOf(arr[i].id) === -1) {
      checked.push(id);
      var children = getNestedChildren(arr, arr[i].id, checked);
      if (children.length) {
        arr[i].children = children;
      }
      out.push(arr[i]);
    }
  }
  return out;

};

console.log(getNestedChildren(arr, 12, []));
pishpish
  • 2,574
  • 15
  • 22
  • Unfortunately for ID:12 it produces only one child, ID 1 – Ketus Apr 16 '16 at 17:31
  • ID 12 indeed has one child ID1. ID1 has four children, they have children and so on until we come back to ID 12. – Ketus Apr 16 '16 at 17:40
  • You want all the children in the same array? Because the children are found, but are stored in `children` property. All you need is one loop to get them all. – pishpish Apr 16 '16 at 17:42
  • That's correct, all in the same array until top level parent is found, so in case of ID 12 list should be populated deep down until it encounters ID 12. – Ketus Apr 16 '16 at 17:48
  • @Ketus You are correct. My code was totally wrong. It's now updated. – pishpish Apr 16 '16 at 18:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109339/discussion-between-ketus-and-janje). – Ketus Apr 16 '16 at 18:44