Apex SFDC99 Final Project Challenge Solution

Here is a fun Apex challenge to test your Apex skills:
https://www.sfdc99.com/2019/01/16/final-project-challenge/

Optimizations:
1. Add static variables to custom variables/custom metadata
2. Cache mapOfZipOwner for faster retrieval + less SOQL queries

Assumption: updating an account owner can only be when:
1. Zip code was empty (null) and now has a value
2. Zip code updated from one zip code to another
3. Zip code updated to the same zip code will NOT update owner

Account Zip Code Trigger

trigger AccountZipCodeTrigger on Account (after insert, after update) {
   Map<Id, Account> accountsZipCodeChanges = new Map<Id, Account>();
   Map<Id, Account> oldAccount = Trigger.oldMap;
   Map<Id, String> oldZipCodes = new Map<Id, String>();
   for (Account acc : Trigger.new){
       if (oldAccount!=null && oldAccount.containsKey(acc.Id)){
        Account oldAccount = oldAccount.get(acc.Id);
        if (acc.BillingPostalCode!=oldAccount.BillingPostalCode){
            accountsZipCodeChanges.put(acc.Id, acc);
            oldZipCodes.put(acc.Id, oldAccount.BillingPostalCode);
        }
       } else if (acc.BillingPostalCode!=null){
           accountsZipCodeChanges.put(acc.Id, acc);
       }
   }
   
   if (!accountsZipCodeChanges.isEmpty()){
       AccountZipCodeHelper.updateOwner(accountsZipCodeChanges, oldZipCodes);
   }
}

Account Zip Code Helper class

public with sharing class AccountZipCodeHelper {

    static Map<String, List<Territory__c>> mapOfZipOwner = new Map<String, List<Territory__c>>();
    static Map<String, Set<Id>> mapOfZipOwnerIds = new Map<String, Set<Id>>();
    
    @TestVisible private static Boolean throwDMLException;
    private static Boolean noExceptionsOccurSendEmail = true;
    
    public static void createTerritoryMap(Map<String, List<Territory__c>> mapOfZipOwner){
        for (Territory__c territory : [select Id, Zip_Code__c, Owner__c from Territory__c Order By Last_Assignment_Received__c ASC NULLS FIRST]){
            if (territory.Zip_Code__c.contains('*')){
                for (Integer k = 0; k <= 9; k++){
                    String zipCodeReplace = territory.Zip_Code__c.replace('*', String.valueOf(k));
                    if (mapOfZipOwner.containsKey(zipCodeReplace)){
                        List<Territory__c> territoryLst = mapOfZipOwner.get(zipCodeReplace);
                        territoryLst.add(territory);
                        mapOfZipOwner.put(zipCodeReplace, territoryLst);
                    } else {
                        mapOfZipOwner.put(zipCodeReplace, new List<Territory__c>{territory});
                    }
                }
            } else {
                if (mapOfZipOwner.containsKey(territory.Zip_Code__c)){
                    List<Territory__c> territoryLst = mapOfZipOwner.get(territory.Zip_Code__c);
                    territoryLst.add(territory);
                    mapOfZipOwner.put(territory.Zip_Code__c, territoryLst);
                } else {
                    mapOfZipOwner.put(territory.Zip_Code__c, new List<Territory__c>{territory});
                }
            }
            
        }
        throwDMLException = false;
    }
    
    static void createTerritoryOwnerIdSet(Map<String, List<Territory__c>> mapOfZipOwners){
        for (String mapOfZipOwner : mapOfZipOwners.keySet()){
             Set<Id> accountOwnerSet = new Set<Id>();
             for (Territory__c territory : mapOfZipOwners.get(mapOfZipOwner)){
                 accountOwnerSet.add(territory.Owner__c);
             }
             mapOfZipOwnerIds.put(mapOfZipOwner, accountOwnerSet);
        }
    }
    
    static {
        createTerritoryMap(mapOfZipOwner);
        createTerritoryOwnerIdSet(mapOfZipOwner);
    }
    
    public static void checkTerritory(List<Territory__c> newTerritories){
        for (Territory__c newTerritory : newTerritories){
            if (mapOfZipOwner.containsKey(newTerritory.Zip_Code__c)){
                List<Territory__c> existingTerritories = mapOfZipOwner.get(newTerritory.Zip_Code__c);
                if (existingTerritories.size() >= 3){
                    newTerritory.addError('Only 3 owners for zip code ' + newTerritory.Zip_Code__c);
                }
            }
        }
    }
    
    public static List<Account> getAccountContactOpporunityFromId(Set<Id> accountIds){
       return [Select Id, OwnerId, (Select Id, OwnerId from Contacts), (Select Id, OwnerId from Opportunities) from Account where Id IN :accountIds];
    }
    
    
    private class TerritoryCompare implements Comparable {
        private Territory__c originalTerritory;
    
        public Integer compareTo(Object obj){
            Territory__c t = (Territory__c)obj;
            Integer compareAssignmentReceived = 0;
            
            if (originalTerritory.Last_Assignment_Received__c < t.Last_Assignment_Received__c){
                compareAssignmentReceived = -1;
            } else if (t.Last_Assignment_Received__c < originalTerritory.Last_Assignment_Received__c){
                compareAssignmentReceived = 1;
            }
            
            return compareAssignmentReceived;
        }
    }
    
    public static List<Territory__c> getRoundRobinTerritoryOwner(List<Territory__c> territories){
       territories.sort();
       territories.get(0).Last_Assignment_Received__c = DateTime.now();
       return territories;
    }

    public static Map<Id, Account> checkAccountsOwnership(List<Account> accounts){
        Map<Id, Account> accountOwnerFixMap = new Map<Id, Account>();
        for (Account acc : accounts){
            if (mapOfZipOwnerIds.containsKey(acc.BillingPostalCode)){
                Set<Id> accountOwnerSet = mapOfZipOwnerIds.get(acc.BillingPostalCode);
                if (!accountOwnerSet.contains(acc.OwnerId)){
                    accountOwnerFixMap.put(acc.Id, acc);
                }
            }
        }
        return accountOwnerFixMap;
    }

    public static void updateOwner(Map<Id, Account> accountsToUpdateOwnerFromZip, Map<Id, String> oldZipCodes){
        List<Account> accountOwnersToUpdate = new List<Account>();
        List<Contact> contactOwnersToUpdate = new List<Contact>();
        List<Opportunity> opportunityToUpdate = new List<Opportunity>();
        Map<Id, Territory__c> territoryUpdate = new Map<Id, Territory__c>();
        List<Assignment_History__c> assignmentHistoryToInsert = new List<Assignment_History__c>();
        
        List<Account> accountContactOppLst = getAccountContactOpporunityFromId(accountsToUpdateOwnerFromZip.keySet());
        for (Account accountToUpdateOwner : accountContactOppLst){
            Account acc = accountsToUpdateOwnerFromZip.get(accountToUpdateOwner.Id);
            
            Id newUserId = null;
            Territory__c newTerritory = null;
            if (mapOfZipOwner.containsKey(acc.BillingPostalCode)){
                List<Territory__c> territoryLst = mapOfZipOwner.get(acc.BillingPostalCode);
                
                List<Territory__c> territoryIndex = getRoundRobinTerritoryOwner(territoryLst);
                newTerritory = territoryIndex.get(0); 
                newUserId = newTerritory.Owner__c;
            }
            
            if (newUserId!=null){
                if (acc.OwnerId != newUserId){
                    accountOwnersToUpdate.add(new Account(Id=accountToUpdateOwner.Id, OwnerId=newUserId, Ownership_Check__c=DateTime.now()));
                    
                    Assignment_History__c assignmentHistory = new Assignment_History__c();
                    assignmentHistory.New_Owner__c = newUserId;
                    assignmentHistory.Previous_Owner__c = acc.OwnerId;
                    assignmentHistory.Changed_By__c = UserInfo.getUserId();
                    assignmentHistory.New_Territory__c = newTerritory.Id;
                    String oldZip = oldZipCodes.get(acc.Id);
                    if (mapOfZipOwner.containsKey(oldZip)){
                        assignmentHistory.Previous_Territory__c = mapOfZipOwner.get(oldZip).get(0).Id;
                    }
                   
                    assignmentHistory.Account__c = accountToUpdateOwner.Id;
                    assignmentHistoryToInsert.add(assignmentHistory);
                    
                    territoryUpdate.put(newTerritory.Id, new Territory__c(Id=newTerritory.Id, Last_Assignment_Received__c=DateTime.now()));
                }
                   
                if (accountToUpdateOwner.contacts!=null){
                    for (Contact con : accountToUpdateOwner.contacts){
                        if (con.OwnerId != newUserId){
                            contactOwnersToUpdate.add(new Contact(Id=con.Id, OwnerId=newUserId));
                        }
                    }
                }
                
                if (accountToUpdateOwner.opportunities!=null){
                    for (Opportunity opp : accountToUpdateOwner.opportunities){
                        if (opp.OwnerId != newUserId){
                            opportunityToUpdate.add(new Opportunity(Id=opp.Id, OwnerId=newUserId));
                        }
                    }
                }
            
            }
        }
        
        SavePoint sp = Database.setSavepoint();
        List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
        try {
           Database.update(accountOwnersToUpdate);
           Database.update(contactOwnersToUpdate);
           Database.update(opportunityToUpdate);
           Database.update(new List<Territory__c>(territoryUpdate.values()));
           List<Database.SaveResult> assignmentInserts = Database.insert(assignmentHistoryToInsert);
           mails = createEmails(assignmentHistoryToInsert, assignmentInserts);
            
           if (throwDMLException){
               throw new DMLException('This is a test dml exception');
           }
          
        } catch(DMLException ex){
            noExceptionsOccurSendEmail = false;
            Database.rollback(sp);
        } 
         
        if (noExceptionsOccurSendEmail){
            Messaging.sendEmail(mails);
        }
          
    }
    
    public static List<Messaging.SingleEmailMessage> createEmails(List<Assignment_History__c> assignmentHistories, List<Database.SaveResult> assignmentInserts){  
        Map<Id, Assignment_History__c> asignmentHistorySuccessMap = new Map<Id, Assignment_History__c>();
        for (Integer k=0; k < assignmentHistories.size(); k++){
            if (assignmentInserts.get(k).isSuccess()){
                asignmentHistorySuccessMap.put(assignmentHistories.get(k).Id, assignmentHistories.get(0));
            }
        }
        
        List<Assignment_History__c> usersToSendEmails = [Select Id, Account__c, New_Owner__r.Email, Previous_Owner__r.Email from Assignment_History__c where Id IN :asignmentHistorySuccessMap.keySet()];
        
        
        List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
        
        for (Assignment_History__c usersToSendEmail : usersToSendEmails){
            if (usersToSendEmail.New_Owner__r.Email!=null && usersToSendEmail.Previous_Owner__r.Email!=null)
                mails.add(sendEmailToBothOwner(usersToSendEmail.Account__c, usersToSendEmail.Previous_Owner__r.Email, usersToSendEmail.New_Owner__r.Email));
        }
    
        return mails;
    }
    
    private static Messaging.SingleEmailMessage sendEmailToBothOwner(Id accId, String fromUserEmail, String toUserEmail){
        Messaging.reserveSingleEmailCapacity(2);
         
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        
        String[] toAddresses = new String[] {fromUserEmail, toUserEmail}; 
        
        // Assign the addresses for the To and CC lists to the mail object.
        mail.setToAddresses(toAddresses);
        
        mail.setReplyTo('youremail@gmail.com');
        
        // Specify the name used as the display name.
        mail.setSenderDisplayName('Account Ownership Support');
        
        // Specify the subject line for your email address.
        mail.setSubject('Account Ownership changed for : ' + accId);
          
        mail.setUseSignature(false);
        
        // Specify the text content of the email.
        mail.setPlainTextBody('Your Account: ' + accId +' has been changed owenership.');
        
        mail.setHtmlBody('Your account:<b> ' + accId +' </b>has changed ownership.

'+
             'To view your case <a href=https://na132.salesforce.com/'+accId+'>click here.</a>');
        
        return mail;
    }
}

Account Zip Code Batch Job

global class AccountZipCodeBatch implements Database.Batchable<sObject>, Database.Stateful {

    global Integer queryLimit = 1000;
    global String queryAccount = 'select Id, OwnerId, BillingPostalCode from Account where BillingPostalCode!=null and (Ownership_Check__c=Yesterday or Ownership_Check__c=null) limit ' + queryLimit;
    global Id currentBatchJobId;
    @TestVisible static Boolean testFlag = true;
    
    global Database.QueryLocator start(Database.BatchableContext BC){
        return Database.getQueryLocator(queryAccount);
    }
    
    global void execute(Database.BatchableContext Bc, List<Account> accounts){
        Map<Id, Account> accountOwnerCheckMap = AccountZipCodeHelper.checkAccountsOwnership(accounts);
        if (accountOwnerCheckMap.size() > 0)
            AccountZipCodeHelper.updateOwner(accountOwnerCheckMap, new Map<Id, String>());
    }
    
    global void finish(Database.BatchableContext Bc){
         if(!Test.isRunningTest()){
            if(Database.query(queryAccount).size()> 0) {
                if (testFlag){
                     currentBatchJobId = Database.executeBatch(this, 200);
                }
            } else if (bc.getJobId()!=null){
                    System.AbortJob(bc.getJobId());
            }
        } else {
            System.abortJob(bc.getJobId());
        }
    }
}

Account Zip Code Test (100% code coverage)

@isTest
public class AccountZipCodeHelperTest {
    
    static List<User> uu = new List<User>();
    static {
        Profile p = [SELECT Id FROM Profile WHERE Name='Standard User']; 
        
        while (uu.size() < 6) {
          Blob b = Crypto.GenerateAESKey(128);
          String h = EncodingUtil.ConvertTohex(b);
          String uid = h.SubString(0,8);
          User u = new User(Alias = uid, Email= uid + '@myorg.com', 
              EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US', 
              LocaleSidKey='en_US', ProfileId = p.Id, 
              TimeZoneSidKey='America/New_York', UserName= uid + '@myorg.com');      
          uu.add(u);
        }
        insert(uu);
    } 
  
    @TestSetup static void setup(){
        Territory__c t1 = new Territory__c(Zip_Code__c='95070', Owner__c=uu.get(0).Id);
        Territory__c t2 = new Territory__c(Zip_Code__c='95071', Owner__c=uu.get(1).Id);
        Territory__c t3 = new Territory__c(Zip_Code__c='95072', Owner__c=uu.get(2).Id);
        Territory__c t4 = new Territory__c(Zip_Code__c='95072', Owner__c=uu.get(3).Id);
        Territory__c t5 = new Territory__c(Zip_Code__c='95075', Owner__c=uu.get(4).Id);
        Territory__c t6 = new Territory__c(Zip_Code__c='95072', Owner__c=uu.get(0).Id);
        Territory__c t7 = new Territory__c(Zip_Code__c='9506*', Owner__c=uu.get(0).Id);
        Territory__c t8 = new Territory__c(Zip_Code__c='95061', Owner__c=uu.get(2).Id);
        List<Territory__c> tLst = new List<Territory__c>{t1, t2, t3, t4, t5, t6, t7, t8};
        insert tLst;
      
        Account acc = new Account(Name='Test Account', BillingPostalCode = '95070');
        insert acc;
        
        Contact con = new Contact(FirstName='First', LastName='Last', Email='youremail@gmail.com', AccountId=acc.Id);
        insert con;
        
        Opportunity opp = new Opportunity(Name='Opp Test', AccountId = acc.Id, StageName='Open', CloseDate=Date.today());
        insert opp;
    }
    
    @isTest static void checkTerritoryMore3Zip(){
        Test.startTest();
            Territory__c t7 = new Territory__c(Zip_Code__c='95072', Owner__c=uu.get(0).Id);
            try{
                insert t7;
            } catch(Exception ex){
                System.assert(ex.getMessage().contains('Only 3 owners for zip code'));
            }
        Test.stopTest();
    }
    
    @isTest static void checkZipCodeUpdate(){
        Account accOwner = [Select Id, OwnerId from Account][0];  
    
        Test.startTest();
            accOwner.BillingPostalCode = '95075';
            update accOwner;
        Test.stopTest();
        
        Id qAccAfter = [Select Id, OwnerId from Account][0].OwnerId;
        Id qConConIdAfter = [Select OwnerId from Contact][0].OwnerId;
        Id qOppOwnerId = [Select OwnerId from Opportunity][0].OwnerId;
        List<Assignment_History__c> assignmentHistories = [Select Account__c, Id, Name, New_Owner__c, New_Territory__c, Previous_Owner__c, Previous_Territory__c from Assignment_History__c];
         
        System.assert(uu.size() == 6);
        
        Territory__c territory = [Select Id, Owner__c from Territory__c where Zip_Code__c='95075'][0];
        
        System.assertEquals(assignmentHistories.size(), 1);
        Assignment_History__c assignmentHistory = assignmentHistories.get(0);
        System.assertEquals(assignmentHistory.Account__c, accOwner.Id);
        System.assertEquals(assignmentHistory.New_Owner__c, territory.Owner__c);
        System.assertEquals(assignmentHistory.New_Territory__c, territory.Id);
        System.assert(assignmentHistory.Previous_Territory__c!=null);
       
        System.assertEquals(qAccAfter, territory.Owner__c);
        System.assertEquals(qconConIdAfter, territory.Owner__c);
        System.assertEquals(qOppOwnerId, territory.Owner__c);
        
        Integer invocations = Limits.getEmailInvocations();
        System.assertEquals(1, invocations, 'An email should be sent');
    }
    
    @isTest static void checkWildCardZipCode(){
        Account accOwner = [Select Id, OwnerId from Account][0];  
    
        Test.startTest();
            accOwner.BillingPostalCode = '95063';
            update accOwner;
        Test.stopTest();
        
        Id qAccAfter = [Select Id, OwnerId from Account][0].OwnerId;
        Id qConConIdAfter = [Select OwnerId from Contact][0].OwnerId;
        Id qOppOwnerId = [Select OwnerId from Opportunity][0].OwnerId;
        List<Assignment_History__c> assignmentHistories = [Select Account__c, Id, Name, New_Owner__c, New_Territory__c, Previous_Owner__c, Previous_Territory__c from Assignment_History__c];
         
        System.assert(uu.size() == 6);
        
        Territory__c territory = [Select Id, Owner__c from Territory__c where Zip_Code__c='9506*'][0];
        
        System.assertEquals(assignmentHistories.size(), 1);
        Assignment_History__c assignmentHistory = assignmentHistories.get(0);
        System.assertEquals(assignmentHistory.Account__c, accOwner.Id);
        System.assertEquals(assignmentHistory.New_Owner__c, territory.Owner__c);
        System.assertEquals(assignmentHistory.New_Territory__c, territory.Id);
        System.assert(assignmentHistory.Previous_Territory__c!=null);
        
        System.assertEquals(qAccAfter, territory.Owner__c);
        System.assertEquals(qconConIdAfter, territory.Owner__c);
        System.assertEquals(qOppOwnerId, territory.Owner__c);
        
        Integer invocations = Limits.getEmailInvocations();
        System.assertEquals(1, invocations, 'An email should be sent');
    }
    
    
     @isTest static void checkZipCodeRoundRobin(){
        Account accOwner = [Select Id, OwnerId from Account][0];
        Id conConId = [Select OwnerId from Contact][0].OwnerId;
        Id oppOwnerId = [Select OwnerId from Opportunity][0].OwnerId;
        
        System.assertEquals(accOwner.OwnerId, conConId);
        System.assertEquals(oppOwnerId, conConId);
        System.assertEquals(oppOwnerId, accOwner.OwnerId);
        
        Test.startTest();
            accOwner.BillingPostalCode = '95072';
            update accOwner;
        Test.stopTest();
        
        Id qAccAfter = [Select Id, OwnerId from Account][0].OwnerId;
        Id qConIdAfter = [Select OwnerId from Contact][0].OwnerId;
        Id qOppOwnerId = [Select OwnerId from Opportunity][0].OwnerId;
        List<Assignment_History__c> assignmentHistories = [Select Account__c, Id, Name, New_Owner__c, New_Territory__c, Previous_Owner__c, Previous_Territory__c from Assignment_History__c];
         
        System.assert(uu.size() == 6);
        
        Set<Id> ownerIds = new Set<Id>();
        for (Territory__c territory :  [Select Id, Owner__c from Territory__c where Zip_Code__c='95072']){
            ownerIds.add(territory.Owner__c);
        }
        
        System.assertEquals(assignmentHistories.size(), 1);
        Assignment_History__c assignmentHistory = assignmentHistories.get(0);
        System.assertEquals(assignmentHistory.Account__c, accOwner.Id);
        //System.assertEquals(assignmentHistory.New_Owner__c, territory.Owner__c);
        //System.assertEquals(assignmentHistory.New_Territory__c, territory.Id);
        System.assert(assignmentHistory.Previous_Territory__c!=null);
        
        System.assert(ownerIds.contains(qAccAfter));
        System.assert(ownerIds.contains(qConIdAfter));
        System.assert(ownerIds.contains(qOppOwnerId));
        
        Integer invocations = Limits.getEmailInvocations();
        System.assertEquals(1, invocations, 'An email should be sent');
        
        System.debug('>>> Test second BillingPostalCode update >>> ');
        //start test round robin assignment
        //have to assign to another postalcode first
        accOwner.BillingPostalCode = '95061';
        update accOwner;
        
        //assign back to 95072 to be assigned to next owner
        accOwner.BillingPostalCode = '95072';
        update accOwner;
        
 
        Id qAccAfterRR = [Select Id, OwnerId from Account][0].OwnerId;
        Id qConIdAfterRR = [Select OwnerId from Contact][0].OwnerId;
        Id qOppOwnerIdRR = [Select OwnerId from Opportunity][0].OwnerId;
        
        System.assert(ownerIds.contains(qAccAfterRR));
        System.assert(ownerIds.contains(qConIdAfterRR));
        System.assert(ownerIds.contains(qOppOwnerIdRR));
        
        System.assert(qAccAfter!=qAccAfterRR);
        System.assert(qConIdAfter!=qConIdAfterRR);
        System.assert(qOppOwnerId!=qOppOwnerIdRR);
        //end test round robin assignment
        
        List<Territory__c> countTerritories = [Select Id from Territory__c where Zip_Code__c='95072' and Last_Assignment_Received__c!=null];
        System.assertEquals(countTerritories.size(), 2);
    }
    
    @isTest static void testDMLExceptionAndRollback(){
        Account accOwner = [Select Id, OwnerId from Account][0];
        Id conConId = [Select OwnerId from Contact][0].OwnerId;
        Id oppOwnerId = [Select OwnerId from Opportunity][0].OwnerId;
        
        AccountZipCodeHelper.throwDMLException = true;
        
        Test.startTest();
            accOwner.BillingPostalCode = '95075';
            update accOwner;
        Test.stopTest();
        
        Id qAccAfter = [Select Id, OwnerId from Account][0].OwnerId;
        Id qConConIdAfter = [Select OwnerId from Contact][0].OwnerId;
        Id qOppOwnerId = [Select OwnerId from Opportunity][0].OwnerId;
        List<Assignment_History__c> assignmentHistories = [Select Account__c, Id, Name, New_Owner__c, New_Territory__c, Previous_Owner__c, Previous_Territory__c from Assignment_History__c];
        
        System.assertEquals(assignmentHistories.size(), 0);
       
        System.assertEquals(qAccAfter, accOwner.OwnerId);
        System.assertEquals(qconConIdAfter, conConId);
        System.assertEquals(qOppOwnerId, oppOwnerId);
        
        Integer invocations = Limits.getEmailInvocations();
        System.assertEquals(1, invocations, 'An email should be sent');
    }
    
    @isTest static void testCheckAccountOwnership(){
        List<Account> accs = [select Id, OwnerId, BillingPostalCode from Account where BillingPostalCode!=null and (Ownership_Check__c=Yesterday or Ownership_Check__c=null) limit 1000]; 
        Map<Id, Account> accountOwnerCheckMap = AccountZipCodeHelper.checkAccountsOwnership(accs);
        System.assertEquals(accountOwnerCheckMap.size(), 1);
        AccountZipCodeHelper.updateOwner(accountOwnerCheckMap, new Map<Id, String>());
        
        List<Account> accsAfter = [select Id, OwnerId, BillingPostalCode from Account where BillingPostalCode!=null and (Ownership_Check__c=Yesterday or Ownership_Check__c=null) limit 1000]; 
        Map<Id, Account> accountOwnerCheckMapAfter = AccountZipCodeHelper.checkAccountsOwnership(accsAfter);
        System.assertEquals(accountOwnerCheckMapAfter.size(), 0);
    }
    
    @isTest static void testOwnershipBatch(){
    
        Test.startTest();
            AccountZipCodeBatch accountZipBatch = new AccountZipCodeBatch();
            Database.executeBatch(accountZipBatch);
        Test.stopTest();
        
        List<Account> accsAfter = [select Id, OwnerId, BillingPostalCode from Account where BillingPostalCode!=null and (Ownership_Check__c=Yesterday or Ownership_Check__c=null) limit 1000]; 
        Map<Id, Account> accountOwnerCheckMapAfter = AccountZipCodeHelper.checkAccountsOwnership(accsAfter);
        System.assertEquals(accountOwnerCheckMapAfter.size(), 0);
    }
    
    @isTest static void testTerritoryCompare(){
         List<Territory__c> territories = [Select Id, Zip_Code__c, Owner__c, Last_Assignment_Received__c from Territory__c];
         territories.sort();
         System.debug('>>> territories before >> ' + territories);
         
         Integer counter = 0;
         for (Territory__c territory : territories){
             if (counter > 1){
                 territory.Last_Assignment_Received__c = DateTime.now().addDays(counter * -1);
             } 
             counter++;
         }
          
         territories.sort();
         System.debug('>>> territories after >> ' + territories);
    }
}

Apex Coding Challenge Find Highest Frequency of Numbers

Problem: Find the number that has the highest frequency in a list of integers.

Input: 1,6,2,1,6,1

Output: 1 //because 1 occurs 3 times in the list

Option 1: Use Hashmap to iterate list

	List<Integer> nums = new List<Integer>{1,6,2,1,6,1};
		Map<Integer, Integer> numMap = new HashMap<>();
		for (Integer num : nums){
			if (numMap.containsKey(num)){
				Integer numFreq = numMap.get(num);
				numMap.put(num, numFreq+1);
			} else {
				numMap.put(num, 1);
			}
		}
		
		Integer biggestFreq = 0;
		Integer biggestVal = 0;
		for (Integer num : numMap.keySet()){
			if (numMap.get(num) > biggestFreq){
				biggestFreq = numMap.get(num);
				biggestVal = num;
			}
		}
		
		System.debug(biggestVal);

Option 2: Use wrapper class with compare to sort wrapper

List<Integer> nums = new List<Integer>{1,6,2,1,6,1};

Map<Integer, NumFrequencyWrapper> numMap = new HashMap<>();
for (Integer num : nums){
	if (numMap.containsKey(num)){
		NumFrequencyWrapper numFreqWrapper = numMap.get(num);
		numFreqWrapper.setFrequency(numFreqWrapper.getFrequency()+1);
		numMap.put(num, numFreqWrapper);
	} else {
		NumFrequencyWrapper numFrequencyWrapper = new NumFrequencyWrapper();
		numFrequencyWrapper.setNum(num);
		numFrequencyWrapper.setFrequency(1);
		numMap.put(num, numFrequencyWrapper);
	}
}
	
List<NumFrequencyWrapper> frequencyWrapperList = new List(numMap.values());
Collections.sort(frequencyWrapperList, new Untitled.NumFrequencyWrapperCompare());
System.debug(frequencyWrapperList.get(0).getNum());

public class NumFrequencyWrapper {
	private Integer num;
	private Integer frequency;
	
	public void setNum(Integer num){
		this.num = num;
	}
	
	public Integer getNum(){
		return num;
	}
	
	public void setFrequency(Integer frequency){
		this.frequency = frequency;
	}
	
	public Integer getFrequency(){
		return this.frequency;
	}
}
	
public class NumFrequencyWrapperCompare implements Comparator<NumFrequencyWrapper>{
	public int compare(NumFrequencyWrapper a, NumFrequencyWrapper b) { 
		return  b.getFrequency() - a.getFrequency(); 
	} 
}  

Option 3: Using buckets to group index of frequencies together

List<Integer> nums = new List<Integer>{1,6,2,1,6,1};
Integer returnNums = 2;
Map<Integer, Integer> numMap = new Map<Integer, Integer>();
for (Integer num : nums){
	if (numMap.containsKey(num)){
		Integer numFreq = numMap.get(num);
		numMap.put(num, numFreq+1);
	} else {
		numMap.put(num, 1);
	}
}

Map<Integer, List<Integer>> mapOfBucketWithValues = new Map<Integer, List<Integer>>();
for (Integer num : numMap.keySet()){
	Integer numFrequency = numMap.get(num);
	if (mapOfBucketWithValues.containsKey(numFrequency)){
		List<Integer> existingIndexNum = mapOfBucketWithValues.get(numFrequency);
		existingIndexNum.add(num);
		mapOfBucketWithValues.put(numFrequency, existingIndexNum);
	} else {
		List<Integer> numList = new ArrayList<>();
		numList.add(num);
		mapOfBucketWithValues.put(numFrequency, numList);
	}
}

for (Integer k=nums.size(), returnedNums=0; 1<=k; k--){
	if (mapOfBucketWithValues.containsKey(k)){
		for (Integer numBucket : mapOfBucketWithValues.get(k)){
			if (returnedNums < returnNums){
				System.debug(numBucket);
				returnedNums++;
			}
		}
	}	
}

Apex Coding Challenge for Iterating Lists

Here is the problem statement:

Given an expectedSum value find the sum (n+ (n+1) = expectedValue) from a list that equals the expectedSum. I am using the following list

1,3,4,4,5,9

and looking for the sum to be 8 (3+5, 4+4)

*Note the list is sorted

Solution 1:


        Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);
		
		for (Integer k=0; k<listInts.size();k++){
			for (Integer j=k+1; j<listInts.size();j++){
				if (listInts.get(k)+listInts.get(j)==expectedSum){
					System.debug(listInts.get(k) + ' + ' + listInts.get(j));
				}
			}
		}	

The above time complexity for a nested for loop is O(n^2)

Solution 2:

Do a binary search to search if the diff is contained in the list


		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);
		for (Integer listInt : listInts){ 
			Integer diffInt = expectedSum-listInt;
			if (binarySearch(listInts, diffInt)!=null){
				System.debug(listInt + " + " + diffInt);
			}
		}
	
	
	public static Integer binarySearch(List<Integer> listInts, Integer searchInt){
		Integer startPos = 0;
		Integer listSize = listInts.size() - 1;
		Integer mid;
		while(startPos <= listSize){
			mid=(startPos+listSize)/2;
			if(listInts.get(mid) == searchInt){
				 return listInts.get(mid);
			}     
			else if(searchInt < listInts.get(mid)){
				listSize = mid - 1;
			}    
			else{
				startPos = mid + 1;
			}
		}
		return null;	 
	}

the above time complexity for binary search is for each element in the array O(n log n) Returning null is not a good practice try to return another number or throw an exception.

Solution 3:

Check if the diff is contained in the list by using the .contains method


		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);

		for (Integer listInt : listInts){
			Integer diffInt =expectedSum-listInt;
			if (listInts.contains(diffInt)){
				System.debug(listInt + ' + ' + diffInt);
			}
		}

the above time complexity for loop is O(n)

Option 4

Start on either end of the array and move inwards when you find a solution, if the sum of the outer and inner element is bigger that the expectedSum move the maxPointer inwards, if it is smaller move the minPointer inwards.

        Integer expectedSum = 1;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);

		Integer maxPointer = listInts.size()-1;
		Integer minPointer = 0;
		for (Integer k=0; k<listInts.size();k++){
			if (expectedSum < listInts.get(maxPointer)){
				maxPointer-=1;
			} else if (minPointer!=maxPointer){
				Integer sumPair =listInts.get(minPointer) + listInts.get(maxPointer);
				if ( sumPair == expectedSum){
					System.debug(listInts.get(minPointer) + " + " + listInts.get(maxPointer));
					minPointer +=1;
					maxPointer-=1;
				} else if (sumPair < expectedSum){
					minPointer +=1;
				} else {
					maxPointer-=1;;
				}
			}
			
		}

the above time complexity for loop is O(n)

Output:

4 + 4
5 + 3
3 + 5

Bonus:

How to do it with an unsorted list:

Iterate through the list and add the integer to the list, if the diff of the expected sum and integer is contained in the set then print it

		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(7,4,6,1,5,2,3);

		Set<Integer> intSetWithDiff = new HashSet<>();
		for (Integer listInt : listInts){
			Integer sumDiff = expectedSum - listInt;
			
			if (intSetWithDiff.contains(sumDiff)){
				System.debug(listInt + " + " + sumDiff);
			}
			intSetWithDiff.add(listInt);		
		}

Output:

1 + 7
2 + 6
3 + 5