Search code examples
javascriptarraysrecursion

Find the parent element of an array by the id of its child


I have a JS array that looks like this:

const arr = [
    {
        i: "tooltip_119",
        children: [
            {
                i: "text_345",
                children: []
            },
            {
                i: "wraper_375",
                children: [
                    {
                        i: "grid_15",
                        children: []
                    }
                ]             
            }
        ]
    },
    {
        i: "chart_123",
        children: []
    },
    {
        i: "graph_467",
        children: []
    },
]

The idea is that such an array can potentially have an infinite number of nestings. And I need a function that, taking the i parameter of some element, will return the i parameter of its parent (or 0 if the element is at the root and has no parents). An example of how such a function works:

console.log(findParent(arr, "grid_15"))  // returns "wraper_375"

I wrote a function like this:

export function findParent(arr, i) {   // this func has a bug on deep levels
  for (let j = 0; j < arr.length; j++) {
    const element = arr[j];
    if (element.children && element.children.length > 0) {
      const childElement = element.children.find((e) => e.i === i);
      if (childElement) {
        return element.i;
      } else {
        const parentElement = findParent(element.children, i);
        if (parentElement) {
          return parentElement.i;
        }
      }
    }
  }
  return 0;
}

The problem is that my function doesn't work at deeper levels of nesting. I would be grateful for help
Expected outputs:

findParent(arr, "tooltip_119")  // 0
findParent(arr, "chart_123")  // 0
findParent(arr, "graph_467")  // 0
// 0 is returned because these elems do not have parent elem 

findParent(arr, "text_345")  // "tooltip_119"
findParent(arr, "wraper_375")  // "tooltip_119"

findParent(arr, "grid_15")  // "wraper_375"

Solution

  • A simple recursion could be like that:

    function findParent(arr, id, parentId = 0) {
      for (const {children, i} of arr) {
        if (i === id){
          return parentId; 
        }
        if (children.length) {
          const childId = findParent(children, id, i);
          if (childId !== null){
            return childId;
          }
        }
      }
      if (parentId === 0) {
        return parentId;
      }
      return null;
    }
    
    ['wrong_id', 'tooltip_119', 'wraper_375', 'grid_15', 'second_childs_child', 'graph_467']
        .forEach(id => console.log(id, '=>', findParent(arr, id)));
    <script>
    const arr = [
        {
            i: "tooltip_119",
            children: [
                {
                    i: "text_345",
                    children: []
                },
                {
                    i: "wraper_375",
                    children: [
                        {
                            i: "grid_15",
                            children: []
                        }
                    ]             
                }
            ]
        },
        {
            i: "chart_123",
            children: [{i: 'second_childs_child', children: []}]
        },
        {
            i: "graph_467",
            children: []
        },
    ]
    </script>

    Another option would be to use a stack to traverse the object:

    const findParentId = (arr, id) => {
    
        let curr = {parentId: 0, arr, j:0};
    
        stack: while (curr) {
    
            let { parentId, arr, j } = curr;
    
            while (j < arr.length) {
                const { children, i } = arr[j];
                curr.j = ++j;
                if (i === id) {
                    return parentId;
                }
                if (children.length) {
                    curr = { parentId: i, arr: children, j: 0, parent: curr };
                    continue stack;
                }
            }
    
            curr = curr.parent;
    
        }
    };
    
    ['wrong_id', 'tooltip_119', 'wraper_375', 'grid_15', 'second_childs_child', 'graph_467']
        .forEach(id => console.log(id, '=>', findParentId(arr, id)));
    <script>
    const arr=[{i:"tooltip_119",children:[{i:"text_345",children:[]},{i:"wraper_375",children:[{i:"grid_15",children:[]}]}]},{i:"chart_123",children:[{i:"second_childs_child",children:[]}]},{i:"graph_467",children:[]},];
    </script>

    And benchmarking:

    ` Chrome/121
    ----------------------------------------------------------------------------
    >                n=3      |     n=30      |      n=300      |     n=3000    
    recursion   1.00x x1m  94 | 1.00x x1m 271 | 1.00x x100k 197 | 1.00x x10k 195
    stack       1.41x x1m 133 | 1.42x x1m 385 | 1.39x x100k 274 | 1.34x x10k 262
    ----------------------------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    

    const $chunk=[{i:"tooltip_119",children:[{i:"text_345",children:[]},{i:"wraper_375",children:[{i:"grid_15",children:[]}]}]},{i:"chart_123",children:[{i:"second_childs_child",children:[]}]},{i:"graph_467",children:[]},];
    
    const $input = [];
    const arr = $input;
    const ids = ['wrong_id', 'tooltip_119', 'wraper_375', 'grid_15', 'second_childs_child', 'graph_467'];
    
    // @benchmark recursion
    
    const findParentId = (arr, id, parentId = 0) => {
      for(const {children, i} of arr){
        if(i === id){
          return parentId;
        }
        if(children.length){
          const childId = findParentId(children, id, i);
          if(childId){
            return childId;
          }
        }
      }  
    };
    // @run
    ids.map(id => findParentId(arr, id));
    
    // @benchmark stack
    
    const findParentId2 = (arr, id) => {
    
        let curr = {parentId: 0, arr, j:0};
    
        stack: while (curr) {
    
            let { parentId, arr, j } = curr;
    
            while (j < arr.length) {
                const { children, i } = arr[j];
                curr.j = ++j;
                if (i === id) {
                    return parentId;
                }
                if (children.length) {
                    curr = { parentId: i, arr: children, j: 0, parent: curr };
                    continue stack;
                }
            }
    
            curr = curr.parent;
    
        }
    };
    // @run
    ids.map(id => findParentId2(arr, id));
    
    /*@end*/eval(atob('e2xldCBlPWRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3Rvcigic2NyaXB0Iik7aWYoIWUubWF0Y2hlcygiW2JlbmNobWFya10iKSl7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7dC5zcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9zaWxlbnRtYW50cmEvYmVuY2htYXJrL2xvYWRlci5qcyIsdC5kZWZlcj0hMCxkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHQpfX0='));