0
\$\begingroup\$

There are approx 43.000 documents (unique nodes). Building a tree structure which has 4 levels.

example of 4 different levels: 13000000 - segment 13010000 - main group 13010100 - group 13010101 - commodity class

I use a angular tree component that need the data to follow below structure.

The code I attached below generate the complete array of objects and the tree is displayed correctly, but it take approx 1 min to generate this structure.

Any help/ideas are much appreciated!

As you see I use multiple of forEach loops to

  1. generate the segment level
  2. generate the children (main groups) of the segment level
  3. generate the children (groups) of the main group level
  4. generate the children (commodity classes) of the group level

Example of the node array that are the final project of this code looks like this

nodes = [{
  "id": "13000000",
  "name": "13 Development (Service)",
  "hasChildren": true,
  "children": [{
    "id": "13010000",
    "name": "1301 Concept development",
    "hasChildren": true,
    "children": [{
      "id": "13010100",
      "name": "130101 Feasibility analysis",
      "hasChildren": true,
      "children": [{
        "id": "13010190",
        "name": "13010190 Feasibility analysis (unspecified)",
        "isLeaf": true
      }]
    }, {
      "id": "13010200",
      "name": "130102 Product definition (concept definition)",
      "hasChildren": true,
      "children": [{
        "id": "13010290",
        "name": "13010290 Product definition (concept definition, unspecified)",
        "isLeaf": true
      }]
    }]
  }]
}]

Example of the raw data coming from the database, used to generate the nodes array above.

data = [
  {
    "_id": "59cbb85b4c4eff813d359ae3",
    "preferredName": "Public safety and military technology",
    "codedName": "49000000"
  },
  {
    "_id": "59cbb85b4c4eff813d359b99",
    "preferredName": "Interior furnishing",
    "codedName": "50000000"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4a",
    "preferredName": "Garden furniture, terrace furniture, balcony furniture",
    "codedName": "50090000"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4b",
    "preferredName": "Outdoor combination",
    "codedName": "50090100"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4c",
    "preferredName": "Outdoor combination (unspecified)",
    "codedName": "50090190"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4d",
    "preferredName": "Outdoor combination (parts)",
    "codedName": "50090191"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4e",
    "preferredName": "Outdoor combination (accessories)",
    "codedName": "50090192"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4f",
    "preferredName": "Outdoor lounge furniture",
    "codedName": "50090200"
  },
  {
    "_id": "59cbb85b4c4eff813d359c50",
    "preferredName": "Outdoor lounge furniture (unspecified)",
    "codedName": "50090290"
  },
  {
    "_id": "59cbb85b4c4eff813d359c51",
    "preferredName": "Outdoor lounge furniture (parts)",
    "codedName": "50090291"
  },
  {
    "_id": "59cbb85b4c4eff813d359c52",
    "preferredName": "Outdoor lounge furniture (accessories)",
    "codedName": "50090292"
  }]

My working code

/* GET WHOLE eCl@ss Tree. */
router.get("/all", (req, res, next) => {
  const nodes = [];
  Eclass.find({}, "level codedName preferredName").sort({ codedName: 1 })
    // Level 1 - segment
    .then(data => {
      data.forEach(item => {
        if (item.codedName !== undefined && item.codedName.substring(2, 4) === "00") {
          const node = {
            id: item.codedName,
            name: `${item.codedName.substring(0, 2)} ${item.preferredName}`,
            hasChildren: true,
            children: []
          };
          nodes.push(node);
        }
      });
      // console.log(nodes);
      return data;
    })
    // Level 2 - main group
    .then(data => {
      nodes.forEach(parent => {
        data.forEach(item => {
          if (item.codedName !== undefined && item.codedName.substring(2, 4) !== "00" && item.codedName.substring(4, 6) === "00") {
            if (parent.id.substring(0, 2) === item.codedName.substring(0, 2)) {
              const node = {
                id: item.codedName,
                name: `${item.codedName.substring(0, 4)} ${item.preferredName}`,
                hasChildren: true,
                children: []
              };
              parent.children.push(node);
            }
          }
        });
      });
      return data;
      // console.log(JSON.stringify(nodes, null, 2));
    })
    // Level 3 - group
    .then(data => {
      nodes.forEach(parent => {
        data.forEach(item => {
          if (item.codedName !== undefined && item.codedName.substring(4, 6) !== "00" && item.codedName.substring(6, 8) === "00") {
            parent.children.forEach(child => {
              if (child.id.substring(0, 4) === item.codedName.substring(0, 4)) {
                const node = {
                  id: item.codedName,
                  name: `${item.codedName.substring(0, 6)} ${item.preferredName}`,
                  hasChildren: true,
                  children: []
                };
                child.children.push(node);
              }
            });
          }
        });
      });
      return data;
    })
    // Level 4 - commodity class
    .then(data => {
      nodes.forEach(parent => {
        data.forEach(item => {
          if (item.codedName !== undefined && item.codedName.substring(6, 8) !== "00") {
            parent.children.forEach(child => {
              child.children.forEach(child => {
                if (child.id.substring(0, 6) === item.codedName.substring(0, 6)) {
                  const node = {
                    id: item.codedName,
                    name: `${item.codedName.substring(0, 8)} ${item.preferredName}`,
                    isLeaf: true
                  };
                  child.children.push(node);
                }
              });
            });
          }
        });
      });
      // console.log(JSON.stringify(nodes, null, 2));
      res.status(200).json(nodes);
    })
    .catch(err => console.error(err));
});
\$\endgroup\$
2
\$\begingroup\$

Looking by the way you make these levels, as a sublevel has the same prefix as its parent, if you sort the data by codedName, you will have data sorted in a way that if you see a n level node, the last n - 1 level node you processed will be its parent.

For example when you iterate after sorting and codedName is 13010190, you know that 13010100 was already processed (because the number is lower) and that any nodes processed between 13010100 and 13010190 will be of level 4, because the next possible level 3 node (13010200) would appear after 13010190.

With this in mind, we can first sort all data by codedName and then just add each node to its lower level last seen node, which will be the right parent for it.

To avoid border cases with level 1 nodes, I chose to make a fake level 0 node called root which will have all level 1 nodes. Its children would be what you need called nodes in your first example.

Also, I didn´t create the properties hasChildren and isLeaf as that can be easily checked by children.length == 0. If its in your requirement to have them, should be easy to add them.

This code would also work for more than 4 levels as long as it follows same format. Here is the code:

data = [
  {
    "_id": "59cbb85b4c4eff813d359ae3",
    "preferredName": "Public safety and military technology",
    "codedName": "49000000"
  },
  {
    "_id": "59cbb85b4c4eff813d359b99",
    "preferredName": "Interior furnishing",
    "codedName": "50000000"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4a",
    "preferredName": "Garden furniture, terrace furniture, balcony furniture",
    "codedName": "50090000"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4b",
    "preferredName": "Outdoor combination",
    "codedName": "50090100"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4c",
    "preferredName": "Outdoor combination (unspecified)",
    "codedName": "50090190"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4d",
    "preferredName": "Outdoor combination (parts)",
    "codedName": "50090191"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4e",
    "preferredName": "Outdoor combination (accessories)",
    "codedName": "50090192"
  },
  {
    "_id": "59cbb85b4c4eff813d359c4f",
    "preferredName": "Outdoor lounge furniture",
    "codedName": "50090200"
  },
  {
    "_id": "59cbb85b4c4eff813d359c50",
    "preferredName": "Outdoor lounge furniture (unspecified)",
    "codedName": "50090290"
  },
  {
    "_id": "59cbb85b4c4eff813d359c51",
    "preferredName": "Outdoor lounge furniture (parts)",
    "codedName": "50090291"
  },
  {
    "_id": "59cbb85b4c4eff813d359c52",
    "preferredName": "Outdoor lounge furniture (accessories)",
    "codedName": "50090292"
  }]
  
  
  
  
sortedData = data.sort((a, b) => Number(a.codedName) - Number(b.codedName))

var latestNodesPerLevel = [{id: "root", children:[]}]

sortedData.forEach(function(item){
	
  if (item.codedName === undefined) return; 
	
  var level = 1;
  var index = 2;
  
  while (index < item.codedName.length && item.codedName.substring(index, index + 2) != 0) {
    index += 2;
    level++;
  }
  
  var node = {
    id: item.codedName,
    children: [],
    name: `${item.codedName.substring(0, index)} ${item.preferredName}`,
  }
  
  if (latestNodesPerLevel[level - 1] === undefined || latestNodesPerLevel[level - 1].id.substring(0, index - 2) != item.codedName.substring(0, index - 2)) {
    console.warn("Parent missing in data", item);
  } else {  
    latestNodesPerLevel[level - 1].children.push(node)
    latestNodesPerLevel[level] = node;
  }
})  

console.log(latestNodesPerLevel[0].children)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.