Oh my god. It's full of code!

Apex – Sorting a map

So this is going to be one of the more apex heavy posts. This is a challenge I think many developers have come across, and while what I propose is by no means the most elegant thing ever, it does do the job until hopefully salesforce implements a native map sorting method. So here is the basic approach

1) populate map with information
2) create another map with the keys as the values you want to sort by, and the value as the key for the other map.
3) create a list whos values are the keyset of the map created in step 2
4) sort the list
5) iterate over the list which now has your values in order, get the value from map 2 using the current loop iterator as the key.

Sounds pretty complicated eh? It’s not SO bad once you kinda get the hang of it, but there is one gotcha that kind of sucks, but we’ll cover that in a minute.

Here is a sample using a simple object called contestEntry. We’ll create a bunch of them with random ordering, and then sort them and loop over the sorted result. You should be able to run this in any org so you can see the principals in action.

        public class contestEntry
        {
            public decimal rank{get;set;}
            public string name{get;set;}
        }
        
        map<string,contestEntry> entries = new map<string,contestEntry>();
        
        contestEntry entry1 = new contestEntry();
        entry1.rank = 5;
        entry1.name = 'Frank';
        entries.put(entry1.name,entry1);
        
        contestEntry entry2 = new contestEntry();
        entry2.rank = 3;
        entry2.name = 'Bob';
        entries.put(entry2.name,entry2);
        
        contestEntry entry3 = new contestEntry();
        entry3.rank = 1;
        entry3.name = 'Jones';
        entries.put(entry3.name,entry3);
        
        contestEntry entry4 = new contestEntry();
        entry4.rank = 4;
        entry4.name = 'Sandy';
        entries.put(entry4.name,entry4);
        
        contestEntry entry5 = new contestEntry();
        entry5.rank = 2;
        entry5.name = 'Felix';
        entries.put(entry5.name,entry5);
        
        //oh no, these entries are all out of order. 
        system.debug(entries) ;
        
        //lets get sorting these guys. First we'll need a map to store the rank, and the contestEntry that rank is 
        //associated with
        map<decimal,string> rankToNameMap = new map<decimal,string>();
        for(contestEntry entry : entries.values())
        {
            rankToNameMap.put(entry.rank,entry.name);
        }
        //now lets put those ranks in a list
        list<decimal> ranksList = new list<decimal>();
        ranksList.addAll(rankToNameMap.keySet());
    
        //now sort them
        ranksList.sort();
        
        //ok, so now we have the ranks in order, we need to figure out who had that rank
        for(decimal rank : ranksList)
        {
            String thisEntryName = rankToNameMap.get(rank);    
            contestEntry thisEntry = entries.get(thisEntryName);
            system.debug(thisEntry);
        }

Walking through it first we just make a sample object to use here. Normally this would be whatever you are actually trying to sort, but for the sake of easyness I just created an object called contestEntry. It just holds a name and a rank.

So then I make a map of those things, keyed by the persona name, and containing the contestEntry object. You might in real life have a map of sObjects keyed by their Id and containing the sObject itself. So then I make a bunch of those and add them to the map in random order so my sorting actually has some work to do 😛

The next thing is creating a map with the key of the value I want to sort this list by. The value is key of the first map. So in real life this might be a dollar amount on an opportunity and then the opportunity Id if the original map was a list of opportunities keyed by their Id.

We loop over all the objects in the original map, and add them to our temporary sorting map, again keyed by value to sort by, and value with key from original map.

Then we create a list of the type of the key of the temporary sorting map. Your key was a decimal? Then your list is of decimals as well, etc. Then add all the keys from the sorting map to the list you just made.

Sort the list.

Iterate over the sorted list, each entry in this list will be a key you can use to get the entry from the sorting map, which will contain the key to the original map. You now have a reference to your original map value by whatever value you sorted on.

There is however one gotcha with this approach. If you have duplicates in the value you are going to sort by, you are going to end up with collisions in your sorting map, which will end up with values overwriting each other and ultimately values missing in your final iteration. This happens because say in my example I have two people with rank 1. First person comes through, their rank is 1, and their name is Sandy. So the sorting map has 1=sandy. Then another person comes through, they also have rank 1 and their name is jones. Now the map has 1=jones. Sandy just fell out of the list. How do you deal with this? The best hack-ish fix I could come up with is to see if the key you are attempting to write to already exists, if so, then write to a slightly higher value key. Basically replace

        for(contestEntry entry : entries.values())
        {
            rankToNameMap.put(entry.rank,entry.name);
        }

with

        for(contestEntry entry : entries.values())
        {
                decimal rank = entry.rank;
                while(rankToNameMap.containsKey(rank))
                {
                    system.debug('------ INCRIMENTING Rank TOTAL FOR ' + entry.name);
                    rank += 0.001;
                }

                rankToNameMap.put(rank,entry.name);
        }

This won’t affect the value displayed when you retrieve and display the object, it just changes the sort order. I haven’t tested this throughly though so don’t rely on it extensively.

Anyway, there you have it Apex fans. A method to reliable (if not quickly or efficiently) sort Apex maps by just about anything. Once you understand the concepts it’s fairly easy to expand to sort to your hearts desire.

5 responses

  1. Pingback: Apex – Sorting a map « Teach Me Salesforce

  2. Regarding the gotcha – this happens to me on many occasions, not just when sorting a map like your example.

    In these situations, I nest my collections. So rather than having a Map, I’ll have Map<Decimal, List>. This requires some extra maintenance – when adding, you need to check if a list already exists; if so then use it, if not then create a new one. A great excuse to trot out everybody’s favourite operator – the ternary operator:

    for(contestEntry entry : entries.values())
    {
    List temp = (rankToNameMap.containsKey(entry.rank))
    ? rankToNameMap.get(entry.rank)
    : new List;

    temp.add(entry.name);
    rankToNameMap.put(rank, temp);
    }

    October 7, 2011 at 11:54 am

  3. Stumbled upon this blog while searching for a similar issue. Basically we want to be able to parametrize classes in such a way you can define your own map for example:

    CustomMap myMap = new CustomMap();

    Sadly you can only parametrize interfaces, but APEX does not allow you to write your own class this way meaning you have to pre-define what variable types your using. Which is annoying as you can image creating a map that you can use like any other map with built-in sorting capacities, iterating capabilities (to return values instead of keys) and more, is simply very useful.

    March 30, 2012 at 12:56 pm

  4. Pingback: Apex – Sorting a map | WikiCloud

  5. Anonymous

    Great solution, thanks for posting it!

    January 23, 2013 at 4:17 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s