0

How can I sort a Map based on value? Here is the Map structure:

Map<DateTime, Integer> AppDetails = new Map<DateTime, Integer>();

And the data:

2016-10-1,10
2016-10-2,3
2016-10-3,13
2016-10-6,40
2016-10-4,60
2016-10-8,40

How can I sort AppDetails by value and get the highest value(60) out of it and return the date(2016-10-4) to the calling function and decrement the original value by 1 (in this case it will be 59) in the Map.

Thanks in advance!

  • Can you please edit your post to use valid syntax? – Adrian Larson Aug 25 '16 at 18:45
  • updated my post.. please see – kiran Aug 25 '16 at 19:06
  • still looking for best solution.. thank all for your suggestion/help.. – kiran Aug 25 '16 at 20:24
  • 1
    You seem to be having an X-Y Problem here. Perhaps step back and try to describe at a high level what you are trying to accomplish. – Adrian Larson Aug 26 '16 at 2:55
1

Here is my attempt:

Map<Date, Integer> appDetails = new Map<Date, Integer>
{
    Date.newInstance(2016,10,1) => 10,
    Date.newInstance(2016,10,2) => 3,
    Date.newInstance(2016,10,3) => 13,
    Date.newInstance(2016,10,6) => 40,
    Date.newInstance(2016,10,4) => 60,
    Date.newInstance(2016,10,8) => 40
};

List<Integer> detailsList = new List<Integer>( appDetails.values() );
detailsList.sort();
Integer maxNumber = detailsList[detailsList.size() - 1];
Integer maxCount = 1;

while (detailsList[detailsList.size() - 1 - maxCount] == maxNumber)
    maxCount++;

for (Date d:appDetails.keySet())
{
    if(appDetails.get(d) == maxNumber)
    {
        appDetails.put(d, maxNumber - 1);
        maxCount--;
        if(maxCount == 0)
            break;
    }
}

It could handle multiple members with max value (if there are more than one) and will decrease each by 1. It also breaks the loop after all members with max value were updated.

5

Maps are an example of an unordered collection, which cannot be sorted.

However, If you're open to writing code and spending additional time developing your idea, you can build something that should work.

From what I see, you don't absolutely require a Map, it was just the closest feature provided by Salesforce that you could find.

Instead of a Map, I'd suggest making an Apex class to hold your data that implements the comparable interface. You can then store these results in a List (which is an ordered collection).

A quick example to get you going

class DatePlusInteger implements Comparable{
    public Date myDate;
    public Integer count;

    public Integer compareTo(Object compareObj){
        DatePlusInteger comparisonTarget = (DatePlusInteger)compareObj;

        if(comparisonTarget.Integer === count){
            return 0;
        }else if(comparisonTarget.Integer < count){
            // This instance of our object has a greater count than the one
            //   we are comparing against. So, return positive 1
            return 1;
        } else {
            return -1;
        }
    }
}

Implementing the Comparable interface will allow you to call List.sort(). Doing so will sort the records in ascending order, meaning that your largest count will be the last element of your list.

The last element of a list is pretty easily retrieved using the List's size() method

// Apex collections are 0-indexed, so when using size(), we must subtract 1
// Otherwise, we'd get an out-of-bounds exception
Object lastElement = someList[someList.size() - 1];

I'm fairly certain that retrieving an object from a List returns a reference to the object instance in that list. To put it simply, that means that you should be able to grab the date, decrement the count, and not have to worry about anything else.

lastElement.myDate;
lastElement.count -= 1;

A slightly better approach would be to add a getter to DatePlusCount that would take care of the decrementing for you.

class DatePlusCount{
    /* other lines omitted */
    public Date getDate(){
        count -=1;
        return myDate;
    }
}

usage is pretty simple

// Assuming List<DatePlusCount> myList is initialized elsewhere...
myList.sort();
Date targetDate = myList[myList.size()-1].getDate();

The only thing left that you'd need to concern yourself with would be to make sure that you always call sort() on the List every single time before trying to retrieve the Date associated with the maximum count.

Swapping the return 1; and return -1; in the compareTo(Object compareObj) method could make things even simpler still. That should effectively cause the list sorting to return in decending order, meaning the max value would be at index 0;

2

Here is another option. At first I thought Comparable would make the most sense, but this might be faster. I haven't profiled them, that's just a guess.

public static void decrementMaxValue(Map<Datetime, Integer> input)
{
    Datetime maxKey;
    Integer maxValue;
    for (Datetime key : input.keySet())
    {
        Integer value = input.get(key);
        if (maxValue == null || (value != null && value > maxValue))
        {
            maxValue = value;
            maxKey = key;
        }
    }
    if (maxKey != null && maxValue != null)
        input.put(maxKey, maxValue - 1);
}

You could probably program this method to be less defensive against NPE. I'll leave that to you to decide.

I profiled the three approaches recommended so far and this one does seem to be the fastest. Here is a Gist detailing my methodology. Your mileage may vary. My results (milliseconds per hundred calls):

method                  Trial_1  Trial_2  Trial_3  Trial_4  Trial_5  Average
decrementMaxComparable       32       33       31       28       26     30.0
decrementMaxInvertMap        17       18       16       19       19     17.8
decrementMaxTrackValue       11       10       11       11       12     11.0
  • It's unlikely to make a difference in most workloads, but I do think that List.sort() uses a mergesort algorithm for sorting, which would be O(n log n) on average vs O(n). – Derek F Aug 25 '16 at 20:45
  • @Derek Sorry, I studied French in college. I think you're saying this approach would be faster than sort? – Adrian Larson Aug 25 '16 at 20:50
  • i have question here -1) after comparing we're adding new record again back to the Map with val -1 , where are we deleting the existing record ? – kiran Aug 25 '16 at 20:58
  • @kiran First of all, a record typically indicates an instance of an sObject. That term is completely out of place here. Second of all, what are you talking about deleting anything? Your OP never makes any mention of such behavior. – Adrian Larson Aug 25 '16 at 21:04
  • @Adrian - sorry for the confusion, because of input.put(maxKey, maxValue - 1); this line i got confused . i just want to mention here - I want to update the existing map record by -1 if its higher. .put method is for adding the new records. correct me if i'm wrong here – kiran Aug 25 '16 at 21:13
1

In this case you have to create a new Map by using AppDetails Map. Like :

Map<integer, date> details = new Map<integer, date>(); for(date d : AppDetails.keySet()) { details.put(AppDetails.get(d), d); }

Now by using new map you can get set. Like : Set<Integer> values = details.keyset();

Now change Set into List.In List you can apply sort() method to sort values in List.'

List<Integer> sortedList = new List<Integer>(values).sort();

Last element is your Max value and by this value you can corresponding date using details map. Like :

Date d = details.get(sortedList[sortedList.size() - 1]);

  • This will run into a problem when two dates end up having the same integer associated with them. This could be fixed by making the details map one of Map<Integer, List<Date>>, but that would also increase the complexity of using and maintaining the details map. – Derek F Aug 25 '16 at 20:03
  • I think this issue will remain with all solutions, in comparable also. – Pragati Jain Aug 25 '16 at 20:06
  • With Comparable, if two records are equal to one another, using List.sort() will put one record ahead of the other using some unknown, arbitrary condition (perhaps based on the hash of the record, I've really no idea). No information in the List will be lost or overwritten. – Derek F Aug 25 '16 at 20:12
  • What I think, in my solution where we are creating new map, we are trying to put duplicate value in map and I think map internally uses hashcode. I am not sure that I am right or wrong. – Pragati Jain Aug 25 '16 at 20:23
  • 1
    You are correct that the keys of a map are stored as a hashcode (an Apex map is basically just a Java Hashmap in the back-end). That doesn't change the fact that if you execute details.put(10, '2016-8-25'); and then details.put(10, '2016-7-1'); the date 2016-8-25 has been overwritten in the map, and can no longer be retrieved. – Derek F Aug 25 '16 at 20:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.