Oleksa O.
Oleksa O.

Reputation: 905

Find last nested n times object by its value

Tried to solve that task myself, but I give up. Whatever I tried, that wouldn't work. Therefore decided to ask help here.

There is an object:

var obj = [{
    "name": "111",
    "type": "folder",
    "expanded": true,
    "id": 0,
    "items": [
      {
        "name": "222",
        "type": "folder",
        "expanded": true,
        "id": 1,
        "items": [
          {
            "name": "333",
            "type": "folder",
            "expanded": true,
            "id": 2,
            "items": [
              {
                "name": "444",
                "type": "folder",
                "expanded": true,
                "id": 3,
                "items": [],
                "itemIndex": 0,
                "index": 0
              }
            ],
            "itemIndex": 0,
            "index": 0
          }
        ],
        "itemIndex": 0,
        "index": 0
      }
    ],
    "itemIndex": 0,
    "index": 0
  }]

The deepness could vary, but the set of keys per each level is always the same. What I need, is to determine the very last possible level.

Taking into account the code above, let's say, I need a function, that, by only having its ID or name, can push one more object into "items" array of the following object:

{
    "name": "444",
    "type": "folder",
    "expanded": true,
    "id": 3,
    "items": [],
    "itemIndex": 0,
    "index": 0
}

In other words, I've got an input text and a button. Upon entering text and clicking that button, a function should determine the very last level of given object and push a newly generated object (same set of keys, ID should be +1 from inherited object, name should be from entered into text field string) into it.

I hope I explained the problem as detailed as possible.

Upvotes: 1

Views: 349

Answers (2)

thebjorn
thebjorn

Reputation: 27351

You'll need a function that finds the deepest node first:

/**
 * Return [depth, item] where item is the deepest subnode of node.
 */
function find_deepest(node, depth=0) {
    if (node.items.length === 0) return [depth, node];

    return node.items.map(child => find_deepest(child, depth+1)).sort().reverse()[0];
}

then it's simply:

const [depth, node] = find_deepest(obj, 0);
node.items.push({...});

update: just realized your obj is a list, so with the above code it would look like this:

const [depth, node] = obj.map(child => find_deepest(child)).sort().reverse()[0]);

which isn't the greatest interface.

Splitting the recursion into two functions, one for the node case, and one for the array case, and wrapping them up in a function that branches on type will give you a better interface:

console.log(find_deepest(obj));

See the snippet below for details (I've expanded your obj with a second branch with empty items:

var obj = [{
    "name": "111", "type": "folder", "expanded": true, "id": 0, "itemIndex": 0, "index": 0,
    "items": [{
        "name": "222", "type": "folder", "expanded": true, "id": 1, "itemIndex": 0, "index": 0,
        "items": [{
            "name": "333", "type": "folder", "expanded": true, "id": 2, "itemIndex": 0, "index": 0,
            "items": [{
                "name": "444", "type": "folder", "expanded": true, "id": 3,"itemIndex": 0, "index": 0,
                "items": []
            }],
        }, {
            "name": "555", "type": "folder", "expanded": true, "id": 3,"itemIndex": 0, "index": 0,
            "items": [{
                "name": "666", "type": "folder", "expanded": true, "id": 3,"itemIndex": 0, "index": 0,
                "items": [{
                    "name": "777", "type": "folder", "expanded": true, "id": 3,"itemIndex": 0, "index": 0,
                    "items": [],
                }]
            }]
        }]
    }]
}];

function find_deepest(val, depth=0) {
    function _find_deepest_node(node, depth) {
        if (node.items.length === 0) return [depth, node];
        return _find_deepest_array(node.items, depth + 1);
    }

    function _find_deepest_array(arr, depth) {
        return arr.map(child => _find_deepest_node(child, depth)).sort().reverse()[0];
    }

    if (Array.isArray(val)) {
        return _find_deepest_array(val, depth)[1];  // get the node, not the depth..
    } else {
        return _find_deepest_node(val, depth)[1];
    }
}

console.log(find_deepest(obj));

Upvotes: 1

Get Off My Lawn
Get Off My Lawn

Reputation: 36341

I would create an recursive function that would push the data to the end of the array once it found the correct id. If it doesn't find the id, go to the next item. It would look like this:

function pushItem(id, data, obj) {
  for (let itm of obj) {
    let r = itm.items.find(i => i.id == id)
    if(!r) return pushItem(id, data, itm.items)
    r.items.push(data)
  }
}

pushItem(3, {abc: 1}, obj)
console.log(obj)

Here is a working example:

var obj = [{
  "name": "111",
  "type": "folder",
  "expanded": true,
  "id": 0,
  "items": [{
    "name": "222",
    "type": "folder",
    "expanded": true,
    "id": 1,
    "items": [{
      "name": "333",
      "type": "folder",
      "expanded": true,
      "id": 2,
      "items": [{
        "name": "444",
        "type": "folder",
        "expanded": true,
        "id": 3,
        "items": [],
        "itemIndex": 0,
        "index": 0
      }],
      "itemIndex": 0,
      "index": 0
    }],
    "itemIndex": 0,
    "index": 0
  }],
  "itemIndex": 0,
  "index": 0
}]

function pushItem(id, data, obj) {
  for (let itm of obj) {
    let r = itm.items.find(i => i.id == id)
    if (!r) return pushItem(id, data, itm.items)
    r.items.push(data)
  }
}

pushItem(3, { name: "123123", id: 20, items: [] }, obj)
console.log(obj)

Upvotes: 0

Related Questions