SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel

Monday 9 August 2021

Binary Tree Real World Problem - Find the Lead Manager | Apex Data Structures Tutorial by SFDC Stop

Hello Trailblazers,


In the previous two posts, we learned about how can we implement a binary tree in apex and how we can insert a new node in a binary tree based on the value of it's parent node. You can have a look at them here:- Binary Tree implementation in Apex and Insertion in Binary Tree based on parent node | Custom comparator in Apex. Now, it's time to put our knowledge in action and solve a real problem. 

Tutorial Videos




Let's have a look at the problem statement in detail:


Problem Statement: Find the Lead Manager


In our salesforce org, we're having a couple of users and as you all must be aware of the fact that each user has a manager field in which we can link another user as a manager of the current user. For simplicity, we're going to use a custom object here named as Employee - Employee__c and each employee will have a Manager field on it - Manager__c which is a self lookup to the Employee itself. You can use User object as well. I am using Employee because I want to create a big hierarchy of records and there are limited number of active users we can have in a developer org.

So, we are solving a number of use cases using our salesforce org. Therefore, each of our employees has the advantage to submit a large number of requests. In order to manage those requests, we have multiple approval processes implemented in our Salesforce org.

Let's consider the below employee hierarchy once:

As you can see above, for our use case, each of the manager can have two employees at max who are reporting directly to the manager, normally what happens in most approval processes, the approval request goes on to the direct manager for the employee (who is linked to the employee using the manager field), but in some cases, we need to send the request to the Lead Manager

What is a Lead Manager?

A Lead Manager is a common manager for two employees according to their hierarchy. For example: In the above hierarchy, we can say that Erlich and Gavin have Richard as their Lead Manager as he is common to both of them in the employee hierarchy.

Therefore, if Erlich want's to book Gavin's time for any help let's say, he needs to send an approval request to the lead manager Richard which is common to both of them and then Richard is going to instruct both Dinesh and Gilfoyle to book the required time for their communication.

Now, given two employee ids, Your task is to find the Lead Manager for those two Employees.

How will you find the Lead Manager for two given employees?

I would highly recommend you to think about this problem and come up with a solution before proceeding ahead. You can use the below code snippet, fill up the method and share it with me. Feel free to create any helper methods or classes if required.
public Id getLeadManager(Id firstEmployeeId, Id secondEmployeeId) {
    // * Add your code here
}
It's time to jump onto the solution now.

Solution: Find the Lead Manager

I am pretty excited to share the solution for this. So, first of all let's divide our problem statement into things that we need to accomplish:

1. Query all the top level employee and all other employees present in the system along with their managers.
2. Find the lead manager for those two employee ids by forming a hierarchy.
3. Return the lead manager id

Step 1

So, our first step is to query all the top level employee and all other employees present in the system along with their managers. Let's query the top level employee by using the query as given below:
Employee__c topEmployee = [SELECT Id FROM Employee__c WHERE Manager__c = null];
Top level employee should not have any manager assigned to him. We'll only proceed ahead if this top level employee is not NULL. In order to find all the other employees along with their managers, we can use a query as given below:
Map<Id, Employee__c> employeesMap = new Map<Id, Employee__c>([
    SELECT Id, (SELECT Id FROM Employees__r) FROM Employee__c
]);
As you can see above, we're querying each employee along with all other employees that are reporting to the current employee. We're storing it as a map so that we can easily get all employees reporting to the manager by using the manager id.

Step 2

Now, it's time to find the lead manager for two employees whose employee ids we're given by forming a hierarchy. For this we're going to construct a binary tree as we're aware that for each employee we can have at max 2 employees that are reporting to the current employee. Once the binary tree is constructed, all we need to do is to find the lowest common ancestor of those two employee nodes in the tree and it'll be the Lead Manager. For ex: if you see in the Binary Tree image shared at the beginning, the lowest common ancestor for Erlich and Gavin is Richard itself and he is the Lead Manager for both of them.

We're going to use the same binary tree code that we updated in our previous post. So, if you want to learn more about how we can construct a binary tree in apex and insert a node by using the value of the parent node. Have a look at my previous posts here:- Binary Tree implementation in Apex and Insertion in Binary Tree based on parent node | Custom comparator in Apex. For reference, I am sharing the full binary tree code again:
/*
*	Author:- Rahul Malhotra
*	Description:- Binary Tree implementation in apex
*	Created Date:- 13-07-2021
*	Last Modified:- 14-07-2021
*       Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public class BinaryTree {

    // * Node class
    public class Node {

        Object data;
        Node left;
        Node right;

        public Node(Object data) {
            this.data = data;
            this.left = NULL;
            this.right = NULL;
        }

        public Object getData() {
            return data;
        }
    }

    // * Root of binary tree
    Node root;

    // * Method to insert data in binary tree
    private void insertData(Object data) {

        // * If root is NULL, create a new node and return
        if(root==NULL) {
            root = new Node(data);
            return;
        }

        // * Initializing a queue of nodes
        List<Node> nodesQueue = new List<Node>();

        // * Adding root node to the queue
        nodesQueue.add(root);

        // * Looping while queue is not empty
        while(!nodesQueue.isEmpty()) {

            // * Getting the current node and removing it from the queue
            Node current = nodesQueue.get(0);
            nodesQueue.remove(0);

            // * If the left child of current node is not null, add it to the queue
            if(current.left!=NULL) {
                nodesQueue.add(current.left);
            }
            // * Otherwise, create a new node and attach it as the left child
            else {
                current.left = new Node(data);
                return;
            }

            // * If the right child of current node is not null, add it to the queue
            if(current.right!=NULL) {
                nodesQueue.add(current.right);
            }
            // * Otherwise, create a new node and attach it to the right child
            else {
                current.right = new Node(data);
                return;
            }
        }

    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to insert data inside a binary tree based on the parent node's data
    */
    public Node insertData(Object data, Object parentData, Comparator comparator) {

        // * If root node is NULL, create a new node, assign it as the root node and return that
        if(root==NULL) {
            root = new Node(data);
            return root;
        }

        // * Otherwise, call the recursive function using the root node
        return insertDataRecursive(root, data, parentData, comparator);
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This helper method is used to insert data inside a binary tree based on the parent node's data
    */
    private Node insertDataRecursive(Node current, Object data, Object parentData, Comparator comparator) {

        // * If current node's data is equal to parentData
        if(comparator.compare(current.data, parentData)) {

            // * If left child of current node is NULL, add a new node as the left child of the current node and return that
            if(current.left==NULL) {
                current.left = new Node(data);
                return current.left;
            } 
            
            // * Else if the right child of current node is NULL, add a new node as the right child of the current node and return that
            else if(current.right==NULL) {
                current.right = new Node(data);
                return current.right;
            } 
            
            // * Otherwise, return NULL as the current node already has two nodes and a new node cannot be inserted
            else {
                return NULL;
            }
        }

        // * If current node's data is not equal to parentData, check in the left and right subtrees

        // * If left subtree exists
        if(current.left!=NULL) {

            Node left = insertDataRecursive(current.left, data, parentData, comparator);

            // * If the node is inserted in the left subtree, return the value received from the left subtree
            if(left!=NULL) {
                return left;
            }
        }

        // * If right subtree exists
        if(current.right!=NULL) {

            Node right = insertDataRecursive(current.right, data, parentData, comparator);

            // * If the node is inserted in the right subtree, return the value received from the right subtree
            if(right!=NULL) {
                return right;
            }
        }

        // * Otherwise, return NULL as the node cannot be inserted
        return NULL;
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to create a binary tree
    */
    public void createBinaryTree(List<Object> elements) {

        // * Inserting elements in binary tree
        for(Object element : elements) {
            insertData(element);
        }
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to print binary tree level by level
    */    
    public void levelOrderPrint() {

        // * Checking if root is not NULL
        if(root!=NULL) {

            String result = '\n';

            // * Initializing a queue
            List<Node> nodesQueue = new List<Node>();

            // * Adding root node to the queue
            nodesQueue.add(root);

            // * Looping while queue is not empty
            while(!nodesQueue.isEmpty()) {

                // * Getting nodes at current level
                Integer nodesAtCurrentLevel = nodesQueue.size();

                // * Looping while nodes at current level is more than 0
                while(nodesAtCurrentLevel>0) {

                    // * Getting the current node and removing it from the queue
                    Node current = nodesQueue.get(0);
                    nodesQueue.remove(0);

                    // * Adding current node data to the result
                    result += current.data + ' ';

                    // * If left child of the current node is not NULL, add it to the queue
                    if(current.left!=NULL) {
                        nodesQueue.add(current.left);
                    }

                    // * If right child of the current node is not NULL, add it to the queue
                    if(current.right!=NULL) {
                        nodesQueue.add(current.right);
                    }

                    // * Decrementing nodes at current level by 1
                    nodesAtCurrentLevel--;
                }

                // * Adding a new line to the result for next level
                result += '\n';
            }

            // * Displaying the result
            System.debug(result);
        } 

        // * If root is NULL, display error message
        else {
            System.debug('Tree not found');
        }
    }
}
So, we have the binary tree ready, let's add one more method in the binary tree which will help us to find the lowest common ancestor.
/*
*  Author:- Rahul Malhotra
*  Description:- This method is used to find lowest common ancestor in a binary tree assuming that both object1 and object2 are present
*/    
public Node lowestCommonAncestor(Object object1, Object object2, Comparator comparator) {
    return lowestCommonAncestor(root, object1, object2, comparator);
}
As you can see above, I have created a simple method which will receive three parameters i.e. two objects: object1 and object2 (because the data of every node in binary tree is of type object) and an instance of comparator class which we're using for comparison. I hope you're aware of the Comparator class as we used it in our previous tutorial, if not, we'll see the code for comparator class later in detail, for now you can consider that the Comparator class has a method which help us to find if two objects are equal or not. Inside the lowestCommonAncestor method shown above, we're calling another method with the same name lowestCommonAncestor which is receiving four parameters in total. Those include the root of binary tree, the two objects and the comparator instance.

The main lowestCommonAncestor method is a recursive method which will start from the root node and will find the lowest common ancestor node inside the binary tree. Before jumping to the code for lowest common ancestor, let's understand the logic first.

How can we find the Lowest Common Ancestor in the Binary Tree?

We start from the root node. Now, we need to find the lowest common ancestor for two nodes that are present in the binary tree. In order to find that, we can have 3 scenarios:

1. Both the nodes are present in the left subtree.
2. Both the nodes are present in the right subtree.
3. One node is present in the left subtree and the other node is present in the right subtree.

In the first case, when both the nodes are present in the left subtree, we'll simply move to the left subtree as the lowest common ancestor will surely be present in the left subtree. 

In the second case, when both the nodes are present in the right subtree, we'll simply move to the right subtree as the lowest common ancestor will surely be present in the right subtree.

In the third case, when one node is present in the left subtree and one node is present in the right subtree that simply means that the current node is the lowest common ancestor for both the nodes

Take a break and think about all these three cases with some examples. Now, let's move on to the code:
/*
*  Author:- Rahul Malhotra
*  Description:- This helper method is used to find lowest common ancestor in a binary tree starting from a particular node
*/    
private Node lowestCommonAncestor(Node current, Object object1, Object object2, Comparator comparator) {        

    // * If the current node is NULL, return NULL
    if(current==NULL) {
        return NULL;
    }

    // * If current node's data is equal to one of object1 or object2, return the current node
    if(
        comparator.compare(current.data, object1) || 
        comparator.compare(current.data, object2)
    ) {
        return current;            
    }

    // * Otherwise, find object1 and object2 in left and right subtree
    Node left = lowestCommonAncestor(current.left, object1, object2, comparator);
    Node right = lowestCommonAncestor(current.right, object1, object2, comparator);

    /* 
    *   If one of object1 or object2 is present in the left subtree and one in right subtree, 
    *   return the current node as it's the lowest common ancestor
    */
    if(left!=NULL && right!=NULL) {
        return current;
    } 
    
    /* 
    *   Else if, both the object1 and object2 are present in the left subtree, 
    *   return the lowest common ancestor returned from the left subtree
    */
    else if(left!=NULL) {
        return left;
    }

    // * Else, return the lowest common ancestor returned from the right subtree
    return right;
}
So, in this method, we're first of all checking that if the current node is NULL, then we return NULL. This means that we've reached ahead of the leaf node and none of the object1 or object2 is present in the current branch of the binary tree. 

If that's not the case, we're comparing object1 with the current node's data (which is also of type object) and also the object2 with the current node's data. If the current node's data is equal to any of object1 or object2, we return the current node. The point to notice is that the current node can also be the lead manager node. Let's understand this with an example. 

Let's say, I need to find the lead manager of Richard and Erlich in the below binary tree:


Then definitely, Richard itself is the lead manager because he is managing Erlich and can approve any request Erlich may have with respect to him. So, at any point of time, if the current node's data is equal to any of object1 or object2, then the current node will be returned as the lowest common ancestor.

If that's not the case, then we're finding the lowest common ancestor in left and right subtree recursively. Note that we're passing current.left and current.right while calling the method recursively for left and right subtree.

Once we have a response from both the subtrees, we need to check that if both the left and right subtrees are returning us some value, it means that one of the employee id is present in the left subtree and one of the employee id is present in the right subtree, therefore, this comes under Scenario Number 3 as stated in the start of this section. So, we're going to return the current node as the lowest common ancestor.

Otherwise, if the left subtree is returning us some value, that means, both the object1 and object2 are present in the left subtree, and we've already got the lowest common ancestor of both the nodes so, we'll simply return the lowest common ancestor received from left subtree as it is.

Finally, if the right subtree is returning us some value, that means, both the object1 and object2 are present in the right subtree, and we've already got the lowest common ancestor of both the nodes so, we'll simply return the lowest common ancestor received from right subtree as it is.

Let's have a look at the updated BinaryTree class once:
/*
*	Author:- Rahul Malhotra
*	Description:- Binary Tree implementation in apex
*	Created Date:- 13-07-2021
*	Last Modified:- 14-07-2021
*       Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public class BinaryTree {

    // * Node class
    public class Node {

        Object data;
        Node left;
        Node right;

        public Node(Object data) {
            this.data = data;
            this.left = NULL;
            this.right = NULL;
        }

        public Object getData() {
            return data;
        }
    }

    // * Root of binary tree
    Node root;

    // * Method to insert data in binary tree
    private void insertData(Object data) {

        // * If root is NULL, create a new node and return
        if(root==NULL) {
            root = new Node(data);
            return;
        }

        // * Initializing a queue of nodes
        List<Node> nodesQueue = new List<Node>();

        // * Adding root node to the queue
        nodesQueue.add(root);

        // * Looping while queue is not empty
        while(!nodesQueue.isEmpty()) {

            // * Getting the current node and removing it from the queue
            Node current = nodesQueue.get(0);
            nodesQueue.remove(0);

            // * If the left child of current node is not null, add it to the queue
            if(current.left!=NULL) {
                nodesQueue.add(current.left);
            }
            // * Otherwise, create a new node and attach it as the left child
            else {
                current.left = new Node(data);
                return;
            }

            // * If the right child of current node is not null, add it to the queue
            if(current.right!=NULL) {
                nodesQueue.add(current.right);
            }
            // * Otherwise, create a new node and attach it to the right child
            else {
                current.right = new Node(data);
                return;
            }
        }

    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to insert data inside a binary tree based on the parent node's data
    */
    public Node insertData(Object data, Object parentData, Comparator comparator) {

        // * If root node is NULL, create a new node, assign it as the root node and return that
        if(root==NULL) {
            root = new Node(data);
            return root;
        }

        // * Otherwise, call the recursive function using the root node
        return insertDataRecursive(root, data, parentData, comparator);
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This helper method is used to insert data inside a binary tree based on the parent node's data
    */
    private Node insertDataRecursive(Node current, Object data, Object parentData, Comparator comparator) {

        // * If current node's data is equal to parentData
        if(comparator.compare(current.data, parentData)) {

            // * If left child of current node is NULL, add a new node as the left child of the current node and return that
            if(current.left==NULL) {
                current.left = new Node(data);
                return current.left;
            } 
            
            // * Else if the right child of current node is NULL, add a new node as the right child of the current node and return that
            else if(current.right==NULL) {
                current.right = new Node(data);
                return current.right;
            } 
            
            // * Otherwise, return NULL as the current node already has two nodes and a new node cannot be inserted
            else {
                return NULL;
            }
        }

        // * If current node's data is not equal to parentData, check in the left and right subtrees

        // * If left subtree exists
        if(current.left!=NULL) {

            Node left = insertDataRecursive(current.left, data, parentData, comparator);

            // * If the node is inserted in the left subtree, return the value received from the left subtree
            if(left!=NULL) {
                return left;
            }
        }

        // * If right subtree exists
        if(current.right!=NULL) {

            Node right = insertDataRecursive(current.right, data, parentData, comparator);

            // * If the node is inserted in the right subtree, return the value received from the right subtree
            if(right!=NULL) {
                return right;
            }
        }

        // * Otherwise, return NULL as the node cannot be inserted
        return NULL;
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to create a binary tree
    */
    public void createBinaryTree(List<Object> elements) {

        // * Inserting elements in binary tree
        for(Object element : elements) {
            insertData(element);
        }
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to print binary tree level by level
    */    
    public void levelOrderPrint() {

        // * Checking if root is not NULL
        if(root!=NULL) {

            String result = '\n';

            // * Initializing a queue
            List<Node> nodesQueue = new List<Node>();

            // * Adding root node to the queue
            nodesQueue.add(root);

            // * Looping while queue is not empty
            while(!nodesQueue.isEmpty()) {

                // * Getting nodes at current level
                Integer nodesAtCurrentLevel = nodesQueue.size();

                // * Looping while nodes at current level is more than 0
                while(nodesAtCurrentLevel>0) {

                    // * Getting the current node and removing it from the queue
                    Node current = nodesQueue.get(0);
                    nodesQueue.remove(0);

                    // * Adding current node data to the result
                    result += current.data + ' ';

                    // * If left child of the current node is not NULL, add it to the queue
                    if(current.left!=NULL) {
                        nodesQueue.add(current.left);
                    }

                    // * If right child of the current node is not NULL, add it to the queue
                    if(current.right!=NULL) {
                        nodesQueue.add(current.right);
                    }

                    // * Decrementing nodes at current level by 1
                    nodesAtCurrentLevel--;
                }

                // * Adding a new line to the result for next level
                result += '\n';
            }

            // * Displaying the result
            System.debug(result);
        } 

        // * If root is NULL, display error message
        else {
            System.debug('Tree not found');
        }
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This method is used to find lowest common ancestor in a binary tree assuming that both object1 and object2 are present
    */    
    public Node lowestCommonAncestor(Object object1, Object object2, Comparator comparator) {
        return lowestCommonAncestor(root, object1, object2, comparator);
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This helper method is used to find lowest common ancestor in a binary tree starting from a particular node
    */    
    private Node lowestCommonAncestor(Node current, Object object1, Object object2, Comparator comparator) {        

        // * If the current node is NULL, return NULL
        if(current==NULL) {
            return NULL;
        }

        // * If current node's data is equal to one of object1 or object2, return the current node
        if(
            comparator.compare(current.data, object1) || 
            comparator.compare(current.data, object2)
        ) {
            return current;            
        }

        // * Otherwise, find object1 and object2 in left and right subtree
        Node left = lowestCommonAncestor(current.left, object1, object2, comparator);
        Node right = lowestCommonAncestor(current.right, object1, object2, comparator);

        /* 
        *   If one of object1 or object2 is present in the left subtree and one in right subtree, 
        *   return the current node as it's the lowest common ancestor
        */
        if(left!=NULL && right!=NULL) {
            return current;
        } 
        
        /* 
        *   Else if, both the object1 and object2 are present in the left subtree, 
        *   return the lowest common ancestor returned from the left subtree
        */
        else if(left!=NULL) {
            return left;
        }

        // * Else, return the lowest common ancestor returned from the right subtree
        return right;
    }
}
Now, our lowestCommonAncestor method is complete. Let's have a look at the comparator class as well:
public abstract class Comparator {
    public abstract Boolean compare(Object o1, Object o2);
}
As you can see above, Comparator is an abstract class which is having a single method that's going to receive two objects and return a Boolean true if both the objects are equal and false if both the objects are not equal. We have created this abstract class to keep our binary tree code independent of data types as we can implement a child of this abstract class and do the comparison there. For example: In our case, we need to compare employee ids, so, we can create another class as shown below:
/* 
*   Author:- Rahul Malhotra
*   Description:- This helper class is used to compare two Ids.
*   It's used to find the lowest common ancestor of binary tree
*/
public class IdCompare extends Comparator {
    public override Boolean compare(Object o1, Object o2) {
        return (Id) o1 == (Id) o2;
    }
}
This IdCompare class is extending our abstract Comparator class and is implementing the compare method. For our binary tree, we're checking if both the objects (when typecasted to ids) are equal or not. If both the ids are equal, the method will return true, otherwise, it'll return false.

Now, whenever we need to find the lowest common ancestor or the lead manager id after constructing the binary tree, we can simply call the method as shown below:
BinaryTree.Node lowestCommonAncestorNode = tree.lowestCommonAncestor(firstEmployeeId, secondEmployeeId, new IdCompare());

I have created an EmployeeHelper class which is having all the code and methods to receive the two employee ids and find out the lead manager id. Let's quickly have a look at the class as a whole once:
/*
*	Author:- Rahul Malhotra
*	Description:- Helper class for Employee related computations
*	Created Date:- 14-07-2021
*	Last Modified:- 14-07-2021
*       Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public with sharing class EmployeeHelper {

    /* 
    *   Author:- Rahul Malhotra
    *   Description:- This helper class is used to compare two Ids.
    *   It's used to find the lowest common ancestor of binary tree
    */
    public class IdCompare extends Comparator {
        public override Boolean compare(Object o1, Object o2) {
            return (Id) o1 == (Id) o2;
        }
    }

    /*
    *  Author:- Rahul Malhotra
    *  Description:- This helper method is used to get the Lead Manager of two employees given the employee id of both the employees
    */    
    public Id getLeadManager(Id firstEmployeeId, Id secondEmployeeId) {

        // * Defining lead manager id and initializing a binary tree
        Id leadManagerId;
        BinaryTree tree = new BinaryTree();

        // * Finding the topmost employee of the hierarchy
        Employee__c topEmployee = [SELECT Id FROM Employee__c WHERE Manager__c = null];

        // * Proceed ahead if top employee is not NULL
        if(topEmployee!=NULL) {

            // * Storing the top employee id
            Id topEmployeeId = topEmployee.Id;

            // * Storing all the employees along with the child employees in the employees map
            Map<Id, Employee__c> employeesMap = new Map<Id, Employee__c>([SELECT Id, (SELECT Id FROM Employees__r) FROM Employee__c]);

            /* 
            *   Initializing a set of ids to store the tree elements, 
            *   a queue of ids as idQueue and creating an instance of IdCompare class
            */
            Set<Id> treeElements = new Set<Id>();
            List<Id> idQueue = new List<Id>();
            IdCompare idCompare = new IdCompare();

            // * Adding the top employee id to the idQueue
            idQueue.add(topEmployeeId);

            // * Inserting topEmployeeId in the tree
            tree.insertData(topEmployeeId, NULL, idCompare);

            // * Looping while idQueue is not empty
            while(!idQueue.isEmpty()) {

                // * Getting the id of employee in the front of queue
                Id currentEmployeeId = idQueue.get(0);
                idQueue.remove(0);

                // * Used to prevent duplicacy in tree
                if(!treeElements.contains(currentEmployeeId)) {

                    // * Adding current employee id to tree elements
                    treeElements.add(currentEmployeeId);

                    // * If the current employee record has child records
                    if(employeesMap.containsKey(currentEmployeeId)) {

                        // * Getting the child records and inserting them in the tree
                        List<Employee__c> childEmployees = employeesMap.get(currentEmployeeId).Employees__r;
                        if(childEmployees!=NULL && !childEmployees.isEmpty()) {
                            for(Employee__c childEmployee : childEmployees) {
                                idQueue.add(childEmployee.Id);
                                tree.insertData(childEmployee.Id, currentEmployeeId, idCompare);
                            }
                        }
                    }
                }
            }

            // * Finding the lead manager in the binary tree using the first employee id and second employee id
            BinaryTree.Node lowestCommonAncestorNode = tree.lowestCommonAncestor(firstEmployeeId, secondEmployeeId, new IdCompare());

            if(lowestCommonAncestorNode!=NULL) {
                // * Storing the lead manager id
                leadManagerId = (Id) lowestCommonAncestorNode.getData();
            }
        }

        // * Returning the lead manager id
        return leadManagerId;
    }
}
First of all, we have the IdCompare class which we've already seen, the second method is getLeadManager() method which is receiving two employee ids and returning the leadManagerId. Inside this method, we defined a variable leadManagerId which will store the lead manager id and then we initialized our binary tree by creating an instance of BinaryTree class.

After that we queried the topmost employee of the hierarchy as we've already discussed that it won't be having any manager. If we found the topmost employee, we're storing it's id in topEmployeeId variable and we're quering all other employees (managers) along with their team mates.

If you remember from the previous tutorial, we have overridden our insertData() method in the BinaryTree class and in the new method we're receiving the current data and the parent node's data to insert a new node in the binary tree. We're going to use the same method to fill up our binary tree here as we're querying all employees along with their managers, so we'll first add the manager id to the binary tree and then the id of employees reporting to the current manager.

For this purpose, I have created a queue named as idQueue which is storing the topEmployeeId i.e. the root node id. Then we're inserting this topEmployeeId in the tree. After that, we're having a loop which will continue while idQueue has some elements. In each iteration of the loop, we're getting the current employee id from the front of the queue and then, we're getting all the employees that are reporting to the current employee. We're then inserting those employees in the idQueue as well as in the tree. In this way, we've all the employee ids inserted into the binary tree. We're also using treeElements set just to make sure that we're not processing an employee id more than once.

Once the binary tree is ready, we're finding out the lowest common ancestor (lead manager) node using the lowestCommonAncestor() method. If we found that node, we're populating the leadManagerId with the value of that node i.e. the employee id. Finally, we're returning the leadManagerId from our method and we're done.

Congratulations!! You've just solved a real world problem using a binary tree. Now, the fun part, it's time for testing..!!

For testing, I have created a large employee hierarchy in my Salesforce org as shown below:
Now, we'll check for Angela and Milana by passing on their employee ids and we'll verify if the lead manager for these two employees is Monica or not.

Let's see the employee records in Salesforce as well:


We're going to run the below code snippet, which is querying records of Angela and Milana and then passing their employee ids in order to find the lead manager id. Once we have the lead manager id, we're querying the lead manager employee record and displaying it's name.
Employee__c angela = [SELECT Id FROM Employee__c WHERE Name =: 'E030'];
Employee__c milana = [SELECT Id FROM Employee__c WHERE Name =: 'E026'];
EmployeeHelper employeeHelper = new EmployeeHelper();
Id leadManagerId = employeeHelper.getLeadManager(angela.Id, milana.Id);
Employee__c employee = [SELECT Name__c FROM Employee__c WHERE Id =: leadManagerId];
System.debug(employee.Name__c);
Let's have a look at the output now:


As you can see in the image above, in the 3rd last line, we're getting the name Monica in the debug, that means we're getting Monica as our Lead Manager for Angela and Milana. So, our code is working perfectly fine.

That's all for this tutorial everyone, I hope you liked it. You can find the full code for this tutorial in the apex-data-structures github repository here.

There can be many other approaches to code this, for example: you can use an interface instead of an abstract class to implement the comparator or you can add conditional logic to find the data type of object and then compare the elements accordingly. I would love to see what solution you come up with. Let me know your thoughts in the comments down below.

Happy Trailblazing..!!

No comments:

Post a Comment