2

I have a list of records that map to a particular date range. For eg:

1 Jan 2020 - 31 Dec 2020 -> Toyota, Aston Martin 
1 Jan 2021 - 31 Dec 2021 -> Red Bull, Mercedes, Ferrari
1 Jan 2022 - 31 Dec 2022 -> Yamaha, Aprillia, Ducati

I require that if a date is picked that lies between the ranges that are in the map, I should get the elements. Eg 21 March 2021 should give me [Red Bull, Mercedes and Ferrari]. I wanted to write a bulkified implementation in a trigger where I could have records across different date ranges. The problem here is that the keys are ranges and I want to fetch based on a date that lies between the ranges.

Is it possible to do something like rangeMap.get(date) which would return me a list of records ? AFAIK, maps work on hash values and I am not sure how I can match a date's hash to a date range's hash value.

One way of getting it done would be to loop over the map keys, determine if the date lies between the range and then use it to get the list of records. Can there be an easier and better way of doing this ?

3
  • Easier and better is subjective. How is your approach One way of getting it done would be to loop over the map keys, determine if the date lies between the range and then use it to get the list of records not easy and/or not good?
    – identigral
    Commented Aug 1, 2022 at 8:32
  • @identigral, while it would get the job done, it would lead to an additional loop. Once the value is acquired, I'd need to loop on them as well to process the records. As I expect a good number of keys as well as values, I wanted to try and avoid nested loops as the trigger is on an object that already has a huge trigger on it and often runs slow.
    – N_H1922
    Commented Aug 1, 2022 at 9:34
  • To avoid the loop, implement the solution via a custom or standard object and SOQL.
    – identigral
    Commented Aug 1, 2022 at 16:42

1 Answer 1

4

tl;dr:

You're gonna have a loop one way or another, be it explicit or implicit.

The long version

It could at least in in theory be done, but neither the Map class nor any Object nor SObject support that out of the box. You'd need to roll that logic yourself.

The .put()/.add() and .get() methods of Map and Set rely on two methods that every object in Apex has, hashCode() and equals(). Using Custom Types in Map Keys and Sets goes over the requirements that those methods should meet (for map and set operations to behave correctly), and you'd add them to your Apex class that you're using to represent a date range.

The tricky (and potentially impossible) bit is going to be implementing .hashCode(). If the hashcode of the thing you pass to .get() doesn't match any of those in your map, .get() stops there. You'd need to come up with some way to turn your date range into a single integer, and for a single date in that range to return the same integer.

Since a single date can be part of an infinite number of ranges, it's impossible to satisfy both of the conditions above (date range hash => single integer, single date => same integer) unless the hashcode is a constant.

After .hashcode() is run, then .equals() is run. Implementing .equals() would be simple enough. It'd just be you comparing your single given date against the start/end dates to see if it is in the range.

From some brief experimenting, Maps (and Sets) work like this:

  • The hashcode is computed (.hashCode() is called) for the object being passed to .add()/.put() or .get()
  • For items being added/put, the record is added to an internal list (ordered iterable collection) under the computed hashcode
    • If multiple items have the same hashcode, the new item is appended to the internal list (so the list can have a size > 1)
  • The internal list is retrieved for the hashcode, and .equals() is called on the items in sequence
    • If equals() returns true, the current item in the internal list is returned
    • Else, if there are still more items in the internal list, we move to the next one and call .equals()
    • When there are no more items, and .equals() returns false, .get() returns null

So in the end, in the general case, you're not going to be able to avoid looping over each date range and doing a comparison. The only difference here is going to be whether you explicitly write that loop, or if you let Apex set up/run the loop on your behalf (with you providing the body of the loop between the .hashCode() and .equals() methods).

If you know that your date ranges will have some specific properties (e.g. they will always be in a single year and never cross a year boundary, as in your given example), then it could be possible to have a non-constant hashcode. It'd take having some number of items in your map to make .get() perform quicker than just looping over the date ranges yourself, but it would be possible to get better performance. The best (average) performance should be when the hashcodes for your date ranges is uniformly distributed.

For those interested, here's the anonymous apex I used:

public class MyClass {
    public Integer hash;
    public Integer hash2;
    
    public integer hashCode(){
        system.debug('in hashCode');
        return hash;
    }

    public Boolean equals(Object other){
        system.debug('in equals, other hash2 = ' + ((MyClass)other).hash2);

        return hash == ((MyClass)other).hash && hash2 == ((MyClass)other).hash2;
    }
}

MyClass inst1 = new MyClass();
inst1.hash = 1;
inst1.hash2 = 1;

MyClass inst2 = new MyClass();
inst2.hash = 1;
inst2.hash2 = 2;

MyClass inst3 = new MyClass();
inst3.hash = 1;
inst3.hash2 = 3;

MyClass inst4 = new MyClass();
inst4.hash = 1;
inst4.hash2 = 4;

MyClass inst5 = new MyClass();
inst5.hash = 1;
inst5.hash2 = 5;

MyClass inst6 = new MyClass();
inst6.hash = 1;
inst6.hash2 = 6;

Set<MyClass> mySet = new Set<MyClass>{
    inst1,
    inst2,
    inst3,
    inst4,
    inst6
};

system.debug('calling get');
//system.debug(mySet.get(inst1));
system.debug(mySet.get(inst6));
1
  • Thank you for the detailed writeup. I did implement the hashcode and the equals functions for my DateRange but I only ran into more issues. Finally ended up with the loop on the keyset.
    – N_H1922
    Commented Aug 10, 2022 at 19:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .