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
Showing posts with label Data Structures. Show all posts
Showing posts with label Data Structures. Show all posts

Saturday, 2 October 2021

First error: Exceeded max size limit of 6000000 OR Exceeded max size limit of 12000000 - Solution

Hello Trailblazers, in this post we're going to talk about the resolution of a very common error as given below:

First error: Exceeded max size limit of 12000000 OR First error: Exceeded max size limit of 6000000 

This error appears when you've exceeded the heap size limit in a single transaction. The heap size limit for a synchronous transaction is 6MB and for an asynchronous transaction is 12MB. Asynchronous transactions basically consists of batch classes, future methods and queueable apex.

How to resolve this error?

You need to reduce the heap size in order to resolve this error. Heap size is basically the memory that your code is taking up while executing. It is the sum of all the memory your variables, maps, sets, lists or any other data structure is consuming. In order to resolve the error you need to reduce the heap size. Below are some tips to help you out:

1. Use Limits.getHeapSize() to understand how much heap size (approx) is used in the current transaction upto a particular point.

2. Use Limits.getLimitHeapSize() to get the total amout of heap size available in the current transaction.

3. Use SOQL For Loops, if you use SOQL for loops the result of the query is given in the batch of 200 records while using a list variable or a single record is returned while using a single instance variable. In both the cases, lesser memory is consumed as we're only storing a maximum 200 records at a time (compared to 50,000 records queried in a single SOQL statement).
List<Account> accounts = [SELECT Id FROM Account];
// Stores 50,000 records at max - greater heap size
for(List<Account> accounts : [SELECT Id FROM Account]) {
}
// Stores 200 records at max - lesser heap size

4. Manage your code effectively using helper functions - Avoid class level variables.
void callMe()
{
    List<Account> accounts = [SELECT Id FROM Account];
    System.debug(Limits.getHeapSize()); // Coming as: 321085
}
callme();
System.debug(Limits.getHeapSize()); // Coming as: 1044

As you can see above, the heap size is reduced after the function call is complete because the call stack is now empty and the local variables are destroyed.

5. Clear the lists/map/set/variables which are not in use or make them null



These are some of the tips that can be used to prevent the heap size limit error. Make sure to write effective code when working with large data. If you have any other suggestion, do comment it down below and I'll include that in the blog post. 

That's all for this tutorial everyone I hope you liked it, the full code used in this post can be found here. Do let me know your feedback/thoughts in the comments down below.

Sharing a Salesforce Help Article related to this error: https://help.salesforce.com/s/articleView?id=000321537&type=1

Happy Trailblazing!!

Friday, 10 September 2021

List Data Structure in Apex | Apex Data Structure Tutorials by SFDC Stop

Hello Trailblazers,


In this post, we're going to learn about how we can use list data structure in apex. A list is a collection of elements or records that you want to store. List in apex can store elements of any data type. It can store: Integer, String, Boolean and any custom data type as well. For example: Account, Contact, Employee__c (Custom Object) etc.

Tutorial Video

The syntax to create a new list is as follows:

List<DataType> listName = new List<DataType>();


Here, DataType should be replaced by the type of data you would like to store and listName should be replaced by the name of the list. For example, if I want to create a list of integers, I can create it as follows:

List<Integer> numbers = new List<Integer>();

The above code snippet will create a list of Integers named as numbers. In this list you can store any number of integers. If you want to add elements to the list at the same moment when a list is initialized, you can do that as well. Consider the below examples:

1. Creating a list of Integers


Code Snippet:

List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);


Output:

2. Creating a list of Strings


Code Snippet:

List<String> names = new List<String>{'Richard', 'Gilfoyle', 'Dinesh', 'Jared', 'Monica'};
System.debug(names);


Output:


In both the above examples, you can see that we've used curly braces {} to specify a comma separated set of values and that values are automatically added to the list. Using this way, you can initialize a list with a set of values. But what if you need to add some more data later on? Let's consider that now:

Adding elements to the list

You can use the add() method to add elements to the list. Let's consider the below example:

Code Snippet:
List<Integer> numbers = new List<Integer>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
System.debug(numbers);

Output:


As you can see above, first of all I initialized a list and then added 5 elements to the list one by one using add() method. Here also, our list is created successfully with 5 elements: 1, 2, 3, 4 and 5.

Accessing elements from the list

Each element in a list is associated with an index starting from 0, for example: the list above contains 5 elements which are stored in the memory as follows:
Let's say you want to access element at index 3, you can simply refer to that by: numbers[3] or you can also use the get() method as: numbers.get(3). Considering the above list, the element at index 3 is: 4. Let's try to code this and see the output:

Code Snippet:
List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Element at index 3 = ' + numbers[3]);
System.debug('Element at index 3 using get() = ' + numbers.get(3));

Output:

As you can see above, I am getting the correct value i.e. 4 in the debug while accessing the element at index 3 by square brackets [] as well as by using the get() method.

Adding element at a particular index

If you want to add element at a particular index, you can use the add() method to do so. Just pass the index as the first parameter and the value as the second parameter to the add() method. For example:


Code Snippet:

List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Adding element "10" at index 3');
numbers.add(3, 10);
System.debug('Updated List = ' + numbers);


Output:

As you can see above, initially the list was: 1, 2, 3, 4, 5. The elements and their indexes for the list are shown below:
Then, we're going to add a new element with value 10 at index 3, so, the values 4 and 5 will be shifted ahead to index 4 and 5 respectively and the updated list will be:

Removing element from the list

We can remove an element from the list by using the remove() method. The remove() method accepts the index of the element that you want to remove. For example: If in the above list you want to remove element 10, then you can pass it's index i.e. 3 in the remove method as follows:

Code Snippet:
List<Integer> numbers = new List<Integer>{1,2,3,10,4,5};
System.debug(numbers);
System.debug('Removing element "10" from index 3');
numbers.remove(3);
System.debug('Updated List = ' + numbers);

Output:

These were the basic methods using which you can interact with the list data structure. There are some other methods as well. I am going to discuss those quickly below with a one line description and code snippets.

Other Commonly used List methods

clear() - Clear all elements in the list

Code Snippet:
List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Clearing list...');
numbers.clear();
System.debug('Updated list = ' + numbers);

Output:
As you can see above, we created a list of numbers, we used clear() method to clear the list. Then we displayed the list got a confirmation that it's empty.

isEmpty() - Returns true if the list is empty, otherwise returns false

Code Snippet:
List<Integer> numbers = new List<Integer>();
System.debug('List Empty --> ' + numbers.isEmpty());
System.debug('Adding "1" to the list');
numbers.add(1);
System.debug('List Empty --> ' + numbers.isEmpty());

Output:
As you can see above, initially, the list was empty, so the isEmpty() method is returning true when called on the numbers list. After that, we added "1" to the list and then the isEmpty() method is returning false.

size() - Returns the size of the list i.e. the total number of elements present in the list

Code Snippet:
List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Size of list = ' + numbers.size());

Output:
As you can see above, I have 5 elements in my list of numbers, so, the size of list is coming out to be 5.

contains() - Returns true if a particular element is present in the list, otherwise, returns false

Code Snippet:
List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('3 is present in the list --> ' + numbers.contains(3));
System.debug('Removing 3 from index 2');
numbers.remove(2);
System.debug(numbers);
System.debug('3 is present in the list --> ' + numbers.contains(3));

Output:
As you can see above, we had 5 elements in the list initially: 1, 2, 3, 4, 5. We checked for element 3 using contains() and it returned true as 3 was present in the list. The element 3 was present at index 2 so, we removed 3 from the list by using remove() method and the updated list is: 1, 2, 4, 5. Finally, we checked for element 3 again if it's present in the list using contains() and this time, the result comes out to be false.

sort() - Sort the elements in the list in ascending order

Code Snippet:
List<Integer> numbers = new List<Integer>{1,4,5,3,2};
System.debug(numbers);
System.debug('Sorting list...');
numbers.sort();
System.debug(numbers);

Output:
As you can see above, initially, the list had elements: 1, 4, 5, 3, 2. Then we sorted the list using sort() and now, the list consist of elements in sorted order i.e.: 1, 2, 3, 4, 5.

These are the most common methods that are used while interacting with a list, although there are some other methods as well, I would suggest to have a look at the official salesforce documentation for more information.

That's all for this tutorial everyone, I hope you liked it. Let me know your feedback or if you have any queries in the comments down below. You can find the whole code snippet used in this tutorial here.


Happy Trailblazing..!!

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..!!

Monday, 19 July 2021

Insertion in Binary Tree based on parent node | Custom comparator in Apex | Apex Data Structures Tutorial by SFDC Stop

Hello Trailblazers,


In this tutorial we're going to see how we can insert a new node in binary tree recursively based on the parent node. We're going to refer the code we created in our previous tutorial and add a new insertData() method which is going to accept the new data, the parent data and a comparator class. Then it's going to compare if the parent data is equal to the data of any node in the binary tree or not, in case we find such a node, we're going to consider that node as our parent node and we'll link the new node as a child of the parent node. If not, we'll return NULL which means the node cannot be inserted in our binary tree.

Tutorial Video


Existing Binary Tree Code

Let's have a quick look at the present code of binary tree from the previous tutorial, before we begin:
/*
*	Author:- Rahul Malhotra
*	Description:- Binary Tree implementation in apex
*	Created Date:- 13-07-2021
*	Last Modified:- 13-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;
        }

    }

    // * 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 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');
        }
    }
}
In the above class, we're going to overload the insertData() method and the new method will insert the data on the basis of parent data. The new insertData() is given below:
/*
*  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);
}
As you can see above, this method is receiving the current data which we need to add to the binary tree, the parent data which is the data of parent node present in the binary tree and a Comparator class instance. The comparator class is a custom comparator which will have a method that can be used to compare two objects and will return true if both the objects are equal and will return false if both the objects are not equal.

This insertData() method is checking if the root node is NULL or not, if it's NULL, we're going to create a new node, assign it as the root node and return that. If the root node exists, we're going to call a private method insertDataRecursive() which is getting the root node, the current object's data and the parent object's data as parameters along with the comparator. Let's have a look at the code of this method to understand how it's working:
/*
*  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;
}
This method will start from the root node and will traverse the whole tree recursively in order to find a node whose data is equal to our parentData. If such a node is found, a new node will be created with the given data and will be inserted as a child of this node, otherwise, NULL will be returned.

As you can see, first of all we're comparing the current node's data with the parentData using the compare() method of comparator class, if both the data are equal, this means that we can create a new node and add it as a child of the current node. For this, we're checking if the left child of the current node is NULL, we can link the new node as the left child of the current node. Otherwise, if it's right child is NULL, we can link the new node as the right child of the current node. If both the left and right child are not NULL, this means that we don't have any space left to link new node with the current node, so, we return NULL here.

If the current node's data is not equal to the parentData, we'll try to insert the node somewhere in the left subtree. If the node is inserted in the left subtree i.e. we get a non NULL value from there, we can simply reutrn that. Otherwise, we can repeat the same process for the right subtree. If we're getting NULL from both the subtrees or if none of left or right subtree exist, this means that, we cannot insert the new node anywhere, therefore we return NULL.

The updated code for the BinaryTree class with both these methods included is given below:
/*
*	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');
        }
    }
}
Now, our insertData() and insertDataRecursive() methods are complete. Let's have a look at the comparator class as well:
/*
*	Author:- Rahul Malhotra
*	Description:- Comparator class to be used in Binary Tree
*	Created Date:- 14-07-2021
*	Last Modified:- 14-07-2021
*       Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
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 abstract method definition, this method is 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: Let's consider that in our case, we need to compare integers, so, we can create another class as shown below:
/* 
*   Author:- Rahul Malhotra
*   Description:- This helper class is used to compare two integers.
*   It's used in the creation of binary tree
*/
public class IntegerCompare extends Comparator {
    public override Boolean compare(Object o1, Object o2) {
        return (Integer) o1 == (Integer) o2;
    }
}
This IntegerCompare 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 Integers) are equal or not. If both the Integers are equal, the method will return true, otherwise, it'll return false.

It's time to test our new insertData() method now. Let's consider the below binary tree.
As you can see above, this is not a complete binary tree as the node 1 has two nodes 2 and 3. But both the nodes 2 and 3 have 1 node each as their child. We can use the below code snippet to create this binary tree and print the nodes level by level using the levelOrderPrint():
IntegerCompare integerCompare = new IntegerCompare();
BinaryTree tree = new BinaryTree();
tree.insertData(1, null, integerCompare);
tree.insertData(2, 1, integerCompare);
tree.insertData(3, 1, integerCompare);
tree.insertData(7, 3, integerCompare);
tree.insertData(4, 2, integerCompare);
tree.levelOrderPrint();
Let's have a look at the output as well:
As you can see above, we're getting the node 1 at level 1, node 2, 3 at level 2 and node 4, 7 at level 3. Therefore, we can say that the code is working perfectly fine and we're able to insert the nodes by finding the parent node.

The actual tree that is created will be a little different from the tree image that we've seen above. It'll look as shown below:
Note that the child node of 3 i.e. node 7 will be on the left side and not on the right side. This is because we're not passing any directions with each node to identify if it should be linked as left or right child and by default as per our code, we're inserting the node as the left child, if the left child of parent node is NULL.

The worst case time complexity of this insertion algorithm will be O(n^2). How? Let's consider the tree given below:
In this case, for insertion of each node we have to traverse through n nodes that are present in the tree. Therefore time taken to insert each node in the tree is O(n). Therefore, for insertion of n nodes, the total time taken will be O(n*n) = O(n^2). Such type of tree is also known as a Skew Tree. We can improve this time complexity by doing a slight modification in the code. Now, that's a homework for you. Think about how you can insert a node in this tree faster and let me know your solution in the comments down below.

The whole code for this tutorial can be accessed in the apex-data-structures Github Repository here.

That's all for this tutorial everyone, I hope you liked it and understood the concept of custom comparator, it's usage and how we can insert a new node in an incomplete or complete binary tree by checking value of the parent node.

Happy Trailblazing..!!

Tuesday, 13 July 2021

Binary Tree implementation in Apex | Apex Data Structures Tutorial by SFDC Stop

Hello Trailblazers,


In this post we're going to talk about how we can create a Binary Tree in Apex. Let's see how a Binary Tree looks like:

As you can see above, we have a root node which is 1 in the above image, and each node in the tree can have two children at max. In the above tree, the nodes: 4 5 6 and 7 are called leaf nodes as they have no children.

Tutorial Video



Let's have a quick look at the full code once, then we'll discuss everything in detail.

/*
*	Author:- Rahul Malhotra
*	Description:- Binary Tree implementation in apex
*	Created Date:- 13-07-2021
*	Last Modified:- 13-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;
        }

    }

    // * 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 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');
        }
    }
}

Creating Node class

    // * Node class
    public class Node {

        Object data;
        Node left;
        Node right;

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

    }
As you can see above, we've created a simple node class. Each node of the binary tree will consist of data and references to the left and right child of the current node.

Insertion in Binary Tree

The insertData method is used to insert a new node in binary tree:
    // * 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;
            }
        }

    }
We have created a data member of the main class named as root which will store the root of a binary tree. Inside insertData method, we're receiving the data as a parameter. After that we're checking, if root is NULL, we create a new node, assign it as the root of the tree and return. 

In case we have a root of the tree defined, we initialize a queue of nodes and add the root node to the queue. This queue is mainly used to traverse the tree level by level so that we can add the new node to the tree at the next available position.

We're looping while the queue is not empty, in each iteration, we get the current node from the queue. After that we check if the left child of the current node is NULL or not. If it's NULL that means we can add our new node at this position and return, if not, we're going to add the left child to the queue and we're going to repeat the same process for the right child.

Level Order Print of Binary Tree

The below method is used to print all nodes of the binary tree level by level:
    /*
    *  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');
        }
    }
In this method as well, we're using the same logic as specified in the insertion method. First of all, we're checking:- If the root node is not NULL, we'll proceed forward, if it's NULL, we're going to simply display the message as: Tree not Found which means we haven't added any element to the binary tree yet. After that we're creating a queue of nodes, inserting the root node in that queue and looping the queue while it's not empty.

In each iteration, we're getting the total number of nodes present in the queue which is nothing but the total number of nodes present at the current level in the binary tree. We're storing that count in nodesAtCurrentLevel variable. Then, we're having another loop which will process all the nodes at the current level together. For each iteration of this inner loop, we're getting the node at the front of the queue and removing it from the queue. Then, we're adding the data of current node to our result string. After that we're checking if the left and right child of the current node is not NULL, if so, we're going to add those to the queue as well. Finally, we're decrementing the nodesAtCurrentLevel variable by 1 as we have processed one node present at the current level.

If you see carefully, the only difference here and in insertion method is that, here we're getting the number of nodes present in the current level, processing all those nodes together and adding a new line in the result string after processing so that we can display all the nodes at the current level in the same line. This is added only for formatting purposes.

At the end, we're displaying the result string.

Creating Binary Tree from a list of elements

Now it's time to create our binary tree using the insertData method. Let's have a look at the below method which will accept the list of elements from the user and will create a binary tree by passing each element one by one into the insertData method.
    /*
    *  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);
        }
    }
This is a fairly simple method where we're iterating all the elements using a for loop and adding them to our binary tree one by one.

Code Execution and Output

Congratulations! You've created a binary tree in apex by yourself. Now, it's time to execute the code and verify the result. Let's have a look at the below code snippet:
BinaryTree tree = new BinaryTree();
tree.createBinaryTree(new List<Integer>{1,2,3,4,5,6,7});
tree.levelOrderPrint();
As you can see above, we're simply creating an object of our BinaryTree class, then we're creating a binary tree by passing a list of integers and we're finally printing the binary tree elements level by level. After executing the above code, you'll get the output as shown below:


As you can see above, the first element of our list is the root node i.e. 1, after that the two elements 2 and 3 are inserted in the tree at the second level as the child of 1. And the other remaining elements, 4 5 6 and 7 are inserted at the third level in the binary tree. These nodes at the last level of the binary tree are called leaf nodes. Therefore, in our above example, 4 5 6 and 7 are leaf nodes.

If you've noticed the full code carefully, you might have observed that we have used Object everywhere as the data type instead of a specific data type such as String or Integer. Object is a generic data type in salesforce which can be used as a template to handle other data types. This is the reason it's working fine when we passed a list of integers to create a tree. Let's pass a list of strings this time to see if that works:
BinaryTree tree = new BinaryTree();
tree.createBinaryTree(new List<String>{'A','B','C','D','E','F','G'});
tree.levelOrderPrint();
As you can see above, this time we are passing a list of strings instead of integers, let's see the output when the above code is executed:


As you can see above, this time also, we're getting the correct output as the tree is created successfully. This means that our code is generic and can be used with different data types.

So, that's all for this tutorial everyone, I hope you learned something new. Try to implement this by your own and let me know if you have any questions in the comments down below. You can find the full code for this tutorial in the apex-data-structures github repository here.

Happy Trailblazing..!!