Mac solved command not found: compdef

Follow these steps to install nvm using homebrew
https://www.wdiaz.org/how-to-install-nvm-with-homebrew/

Confirm echo $HOME/.nvm returns
/Users/{your username}/.nvm

Now let’s edit zshrc to add nvm bash_completion

vi ~/.zshrc

Add the following lines to the top of the file

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

autoload -Uz compinit
compinit

Note: Make sure you add this snippet before any call to compdef else you will still see the error

Reload the terminal and the errors should be gone

Apex Coding Interview Challenge #11

Given an integer k and a string s, find the length of the longest substring that contains at most k distinct characters.

For example, given s = “abcba” and k = 2, the longest substring with k distinct characters is “bcb”.

Solution

public static String getLongestSubstringDistinct(String str, Integer k){
    Integer n = str.length();
    
    Integer left = 0;
    Integer right = 0;
    
    Map<Integer, Integer> mapOfChars = new Map<Integer, Integer>();
    
    while (right < n){
        if (mapOfChars.size() < k+1){
            if (!mapOfChars.containsKey(str.charAt(right))){ 
               mapOfChars.put(str.charAt(right), right);           
            } else if (right-mapOfChars.get(str.charAt(right))<=1){
                mapOfChars.put(str.charAt(right), right);  
            }
              
            right++;
        }
        System.debug('mapOfChars > ' + mapOfChars);
        
        if (mapOfChars.size() == k+1){
            List<Integer> mapOfValues = mapOfChars.values();
            mapOfValues.sort();
            Integer leftMax = mapOfValues.get(0);
            
            mapOfChars.remove(str.charAt(leftMax));
            left = leftMax + 1;
        }
    }
    
    List<Integer> charStartEnd = mapOfChars.values();
    charStartEnd.sort();
    return str.subString(charStartEnd.get(0)-1, charStartEnd.get(1));
}

Testing

System.debug(getLongestSubstringDistinct('abcba', 2)); //bcb
System.debug(getLongestSubstringDistinct('zxybobc', 2)); //bob

Apex Coding Interview Challenge #10

Given a string of round, curly, and square open and closing brackets, return whether the brackets are balanced (well-formed).

For example, given the string “([])[]({})”, you should return true.

Given the string “([)]” or “((()”, you should return false.

There is are Stack implementation currently available in Apex so we can use a List to push and pop characters

Solution

public class Stack {
    private List<Object> items {get; set;}
    
    public Stack() {
        this.items = new List<Object>();
    }
    
    public Integer size() {
        return this.items.size();
    }

    public Boolean isEmpty() {
        return size() == 0;
    }
        
    public void push(Object itemToPush) {
        this.items.add(itemToPush);
    }
    
    public Object pop() {
        if (isEmpty()) {
            throw new EmptyStackException();
        }
        
        return this.items.remove(size() - 1);
    }
    
    public Object peek() {
        if (isEmpty()) {
            throw new EmptyStackException('Stack is empty');
        }
        
        return this.items.get(size() - 1);
    }    
}

public class EmptyStackException extends Exception {}

public static Map<String, String> mapBrackets(){
    Map<String, String> bracketMap = new Map<String, String>();
    bracketMap.put('}', '{');
    bracketMap.put(')', '(');
    bracketMap.put(']', '[');
    return bracketMap;
}

public static Boolean checkClosingBrackets(String bracketStr) {
    Map<String, String> bracketMap = mapBrackets();
    Stack customStack = new Stack();
    
    for (Integer i=0; i < bracketStr.length(); i++){
        String str = bracketStr.substring(i,i+1);
        System.debug('> ' + str);
        if (!bracketMap.containsKey(str)){
            customStack.push((String)str);
        } else {
            String pop = (String)customStack.pop();
            String mappedVal = bracketMap.get(str);
            
            if (!mappedVal.equals(pop)){
                return false;
            }
        }
    }
    
    if (!customStack.isEmpty()){
        return false;
    }
    
    return true;
}

Testing

System.debug(checkClosingBrackets('([])[]({})')); //true
System.debug(checkClosingBrackets('([])[]({}'));  //false

Apex Coding Interview Challenge #9

Given a list of integers, write a function that returns the largest sum of non-adjacent numbers. Numbers can be 0 or negative.

For example, [2, 4, 6, 2, 5] should return 13, since we pick 2, 6, and 5. [5, 1, 1, 5] should return 10, since we pick 5 and 5.

Follow-up: Can you do this in O(N) time and constant space?

Solution

public Integer sumLargestNonAdjacentNumbers(List<Integer> lstNumbers){
      Integer exclusive = 0;
      Integer inclusive = lstNumbers.get(0);
      for (Integer i = 1; i < lstNumbers.size(); i++) {
        Integer temp = inclusive;
        inclusive = Math.max(exclusive + lstNumbers.get(i), inclusive);
        exclusive = temp;
      }
      return inclusive;
}

Testing

System.debug(sumLargestNonAdjacentNumbers(new List<Integer>{5, 1, 1, 5})); //10

System.debug(sumLargestNonAdjacentNumbers(new List<Integer>{2, 4, 6, 2, 5})); //13

Complexity Analysis
Time complexity: O(n)
Space complexity: O(1)

Apex Coding Interview Challenge #8

Implement an autocomplete system. That is, given a query string s and a set of all possible query strings, return all strings in the set that have s as a prefix.

For example, given the query string de and the set of strings [dog, deer, deal], return [deer, deal].

Hint: Try preprocessing the dictionary into a more efficient data structure to speed up queries.

Solution

public Map<String, List<String>> mapOfDictionary(List<String> wordsLst){
    Map<String, List<String>> dictionaryMap = new Map<String, List<String>>();
    
    for (String w : wordsLst){
        List<String> wordChars = new List<String>(w.split(''));
        String concatChars = '';
        for (String wordChar : wordChars){
            concatChars += wordChar;
            if (dictionaryMap.containsKey(concatChars)){
                List<String> wordsForChars = dictionaryMap.get(concatChars);
                wordsForChars.add(w);
                dictionaryMap.put(concatChars, wordsForChars);
            } else {
                dictionaryMap.put(concatChars, new List<String>{w});
            }
        }   
    }
   
    return dictionaryMap;
}

public List<String> getWordsForAutoComplete(String word){
    Map<String, List<String>> dictionaryMap = mapOfDictionary(new List<String>{'dog', 'deer', 'deal'});
    return dictionaryMap.get(word);
}

Testing

System.debug(getWordsForAutoComplete('de')); //(deer, deal)

Complexity Analysis
Time complexity: O(n^2)
Space complexity:  O(m*n) //m – max length of string, n – amount of words

Apex Coding Interview Challenge #7

Given an array of integers, find the first missing positive integer in linear time and constant space. In other words, find the lowest positive integer that does not exist in the array. The array can contain duplicates and negative numbers as well.

For example, the input [3, 4, -1, 1] should give 2. The input [1, 2, 0] should give 3.

Solution

public Integer lowestIntMissingFromLst(List<Integer> intVals){
  Integer missingNum = 0;
  Integer maxVal = 0;
  Integer maxMin = 0;
  Integer maxMax = 0;
  for (Integer k = 0; k < intVals.size(); k++){
      if (k == 0){
          maxVal = intVals.get(k);
      } else if (maxVal < intVals.get(k)){
          maxVal =  intVals.get(k);
          maxMin = intVals.get(k) - intVals.size();
          maxMax = intVals.get(k) + intVals.size();
      }
      
      Integer plusOne = intVals.get(k)+1;
      Integer minusOne = intVals.get(k)-1;
      
      if (plusOne < maxMax && !intVals.contains(plusOne)){
          missingNum = plusOne;
      } else if (minusOne < maxMin && !intVals.contains(minusOne)){
          missingNum = minusOne;
      }
  }
  return missingNum;
}

Test

System.debug(lowestIntMissingFromLst(new List<Integer>{3, 4, -1, 1})); //2

System.debug(lowestIntMissingFromLst(new List<Integer>{1, 2, 0})); //3

Complexity Analysis
Time complexity: O(n)
Space complexity: O(1)

Apex Coding Interview Challenge #6

Given an array of integers, return a new array such that each element at index i of the new array is the product of all the numbers in the original array except the one at i.

For example, if our input was [1, 2, 3, 4, 5], the expected output would be [120, 60, 40, 30, 24]. If our input was [3, 2, 1], the expected output would be [2, 3, 6].

Solution

public List<Integer> productOfNumbers(List<Integer> intVals){
   List<Integer> productLst = new List<Integer>();
   for (Integer k = 0; k < intVals.size(); k++){
     Integer sumOfVals = 1; // it's 1 because multiplying by 0 will always be 0
     for (Integer j = 0; j < intVals.size(); j++){
      if (j != k){
        sumOfVals *= intVals.get(j);
      }
     }
    productLst.add(sumOfVals);
   }
  return productLst;
}

Test

System.debug(productOfNumbers(new List<Integer>{1, 2, 3, 4, 5})); //(120, 60, 40, 30, 24)

System.debug(productOfNumbers(new List<Integer>{3, 2, 1})); //(2, 3, 6)

Complexity Analysis
Time complexity: O(n2)
Space complexity: O(n)

Apex Coding Interview Challenge #2

Given a list of numbers and a number k, return whether any two numbers from the list add up to k.

For example, given [10, 15, 3, 7] and k of 17, return true since 10 + 7 is 17.

Bonus: Can you do this in one pass?

Solution

public Boolean checkNumberAddToK(List<Integer> intVals, Integer k){
   Boolean sumAddsUpFlag = false;
   Set<Integer> diffVals = new Set<Integer>();
   for (Integer intVal : intVals){
     if (diffVals.isEmpty()){
       diffVals.add(k-intVal);
     } else {
       if (diffVals.contains(intVal)){
         sumAddsUpFlag = true;
         break; 
       } else {
         diffVals.add(k-intVal);
       }
     }
   }

   return sumAddsUpFlag;
}

Test

System.debug(checkNumberAddToK(new List{10, 15, 3, 7}, 17)); //return true

System.debug(checkNumberAddToK(new List{10, 15, 3, 8}, 17)); //return false

Complexity Analysis
Time complexity: O(n)
Space complexity: O(n)

Apex Coding Interview Challenge #1

This question was asked during an Amazon interview

Following schema is provided:

Account

Total_Salary__c (Number)

Max_Salary__c (Number)

Account_Salary__c

Account__c (lookup)

Name (String)

Salary__c (Number)

An Account can have multiple Account_Salary__c records that lookup to an Account by the Account__c.

Write a trigger that would update the Account Total_Salary__c, Max_Salary__c when a new Account salary record is:

  1. Inserted
  2. Update
  3. Deleted
  4. Undeleted

Declarative Programming solution

  1. Master Detail relationship between Account and Account_Salary__c, use sum(Salary__c) and max(Salary__c) to do rollup to Account
  2. Process Builder or Flow that calls out to @InvocableMethod to query all other Account_Salary__c records and makes the update

Imperative Programming solution 

Trigger

trigger AccountSalaryTrigger on Account_Salary__c (after insert, after update, after delete, after undelete) {
    if (Trigger.isUpdate){
        AccountSalaryHelper.updateAccount(Trigger.new, Trigger.oldMap);
    } else if (Trigger.isDelete){
       AccountSalaryHelper.updateAccount(Trigger.old, null);
    } else {
       AccountSalaryHelper.updateAccount(Trigger.new, null);
    }
}

Helper class

public with sharing class AccountSalaryHelper {

    public static void updateAccount(List<Account_Salary__c> newAccountSalaries, Map<Id, Account_Salary__c> oldMap){
        Set<Id> accountIds = new Set<Id>();
        for (Account_Salary__c newAccountSalary : newAccountSalaries){
            if (oldMap!=null){
                Account_Salary__c oldAccountSalary = oldMap.get(newAccountSalary.Id);
                if (oldAccountSalary.Salary__c != newAccountSalary.Salary__c){
                    accountIds.add(newAccountSalary.Account__c);
                }
            } else {
                accountIds.add(newAccountSalary.Account__c);
            }
        }
        
        if (!accountIds.isEmpty()){
            List<AggregateResult> aggResults = [Select Account__c accId, sum(Salary__c) sumSalary, max(Salary__c) maxSalary from Account_Salary__c where Account__c IN :accountIds Group By Account__c];
            
            List<Account> accountsToUpdate = new List<Account>();
            for (AggregateResult aggResult : aggResults){
                Id accountId = (Id)aggResult.get('accId');
                if (accountId!=null){
                    Account updateAccount = new Account();
                    updateAccount.Id =accountId;
                    updateAccount.Total_Salary__c=(Decimal)aggResult.get('sumSalary');
                    updateAccount.Max_Salary__c = (Decimal)aggResult.get('maxSalary');
                    accountsToUpdate.add(updateAccount);
                }
            }
            
            if (!accountsToUpdate.isEmpty()){
                SavePoint sp = Database.setSavePoint();
                try{
                    update accountsToUpdate;
                } catch(DMLException ex){
                    Database.rollback(sp);
                }
            }
        }
    }
}

Helper Test class

@isTest
private class AccountSalaryHelperTest {

    @TestSetup static void setup(){
        Account acc = new Account();
        acc.Name = 'Test';
        insert acc;
    
        Account_Salary__c as1 = new Account_Salary__c();
        as1.Name='as1';
        as1.Account__c = acc.Id;
        as1.Salary__c = 500;
        insert as1;
        
        Account_Salary__c as2 = new Account_Salary__c();
        as2.Name = 'as2';
        as2.Account__c = acc.Id;
        as2.Salary__c = 700;
        insert as2;
    }

    @isTest static void testInsertAccountSalary(){
        Account acc = [Select Id, Max_Salary__c, Total_Salary__c from Account][0];
        
        Test.startTest();
            Account_Salary__c as3 = new Account_Salary__c();
            as3.Name = 'as3';
            as3.Account__c = acc.Id;
            as3.Salary__c = 300;
            insert as3;
        Test.stopTest();
        
        Account accAfter = [Select Id, Max_Salary__c, Total_Salary__c from Account][0];
        System.assertEquals(accAfter.Max_Salary__c, 700);
        System.assertEquals(accAfter.Total_Salary__c, 1500);
    }
    
    @isTest static void testUpdateAccountSalary(){
        Account_Salary__c accSalary = [Select Id, Salary__c from Account_Salary__c where Name='as2'][0];
         Test.startTest();
            accSalary.Salary__c = 800;
            update accSalary;
        Test.stopTest();
        
        Account acc = [Select Id, Max_Salary__c, Total_Salary__c from Account][0];
        System.assertEquals(acc.Max_Salary__c, 800);
        System.assertEquals(acc.Total_Salary__c, 1300);
    }
    
    @isTest static void testDeleteAccountSalary(){
        Test.startTest();
            delete [Select Id from Account_Salary__c where Name='as2'][0];
        Test.stopTest();
        
        Account acc = [Select Id, Max_Salary__c, Total_Salary__c from Account][0];
        System.assertEquals(acc.Max_Salary__c, 500);
        System.assertEquals(acc.Total_Salary__c, 500);
    }
}

Follow up question
1. How can we make the trigger more dynamic so when a new field is added it would do the max and sum on account

Answer:
Create a custom metadata mapper table that would contain the SOQL query values and then related Account mapped fields. Create a dynamic SOQL query reading the fields that needs to be queried from custom metadata. Use the SObject set method to set the field values. Account.put(‘Total_Salary_Count__c’, (Decimal)aggResult.get(‘countSalary’));

Apex Pass by Reference Pass by Value Examples

Pass by value – all primitive data type arguments, such as Integer or String, are passed into methods by value. This means that any changes to the arguments exist only within the scope of the method. When the method returns, the changes to the arguments are lost.

Non-primitive data type arguments, such as sObjects, are also passed into methods by value. This means that when the method returns, the passed-in argument still references the same object as before the method call, and can’t be changed to point to another object. However, the values of the object’s fields can be changed in the method.

Pass by reference means when a method is called, that actual variable is passed to the method

Pass by reference

A map with String key and List as value

 
Map<String, List<String>> mapOfLst = new Map<String, List<String>>();
mapOfLst.put('test', new List<String>{'1'});
System.debug('test 1 > ' + mapOfLst.get('test')); // (1)
List<String> lstOfMap = mapOfLst.get('test');
lstOfMap.add('2');
System.debug('test 2 > ' + mapOfLst.get('test')); // (1, 2)

Note we do not have to put the list back to the map, like this:
mapOfLst.put(‘test’, lstOfMap);

This is unnecessary as the List is passed by reference and we can just add the value to the list and will be available in the map when we do a get by key

public class StrUtil {
    private String str;
    
    public StrUtil(String str){
        this.str = str;
    }
    
    public void setStr(String str){
        this.str = str;
    }
}

Map<String, StrUtil> mapOfLst = new Map<String, StrUtil>();
mapOfLst.put('test', new StrUtil('1'));
System.debug('test 1 > ' + mapOfLst.get('test')); //StrUtil:[str=1]
StrUtil str = mapOfLst.get('test');
str.setStr('2');
System.debug('test 2 > ' + mapOfLst.get('test')); //StrUtil:[str=2]

Works same when we use objects no need to put the update object back to map as it is passed by reference

Pass by value

Map<String, String> mapOfLst = new Map<String, String>();
mapOfLst.put('test', '1');
System.debug('test 1 > ' + mapOfLst.get('test')); //1
String str = mapOfLst.get('test');
str = '2';
System.debug('test 2 > ' + mapOfLst.get('test')); //1

Pass by value will not update the value of the map and we need to put value back to map
mapOfLst.put(‘test’, str);

Account acc = [Select Id, Name from Account limit 1][0];

public static Account setAccountName(Account acc){
    acc.Name = 'Update Name';
    return acc;
}

System.debug('>> ' + setAccountName(acc)); //still same account but Name field was changed

SObject pass by value, we can update the SObject fields but still reference the same SObject