Home > other >  How can I sort a Map<String, List<CustomObject>>?
How can I sort a Map<String, List<CustomObject>>?

Time:01-27

I have done a lot of research for this question, but I have not found a way to sort a map of custom object lists (Map<String, List<CustomObj>>), basing the comparison on CustomObj attributes (as SORT_BY_NAME, SORT_BY_DATE, etc).

A motivating example of my question is:

  • I have a custom object: Person (with attribute as Name, DateOfBith, etc ...);
  • I have a Map of Person object List as: Map<String, List<Person>>. The map key is a String used for other purposes;
  • I would like to create a comparator and a sorting method that sorts the map in ascending order based on comparisons between the attributes of the Person object (name, date, etc ..)

For simplicity I report the real code but adapted to a simplified case of Person object, because it would already represent the concept of entity.

Person.java -> Custom object

public class Person {

     private String name;
     private Date dateOfBirth;
     ...

     // Empty and Full attrs Constructors
     ...

     // Getter and Setter
     ...

     // Comparator by name
     public static Comparator<Person> COMPARE_BY_NAME = Comparator.comparing(one -> one.name);
     // Comparator by date
     public static Comparator<Person> COMPARE_BY_DATE = Comparator.comparing(one -> one.dateOfBirth);

}

Sorter.java -> Sorter object

public class Sorter {

     // List Comparator of Person by Date 
     public static final Comparator<? super List<Person>> COMPARATOR_BY_DATE = (Comparator<List<Person>>) (p1, p2) -> {
          for (Persontab person1: p1) {
              for (Person person2: p2) {
                  return Person.COMPARE_BY_DATE.compare(person1, person2);
              }
          }
          return 0;
     };

     // List Comparator of Person by Name
     public static final Comparator<? super List<Person>> COMPARATOR_BY_NAME = (Comparator<List<Person>>) (p1, p2) -> {
          for (Persontab person1: p1) {
              for (Person person2: p2) {
                  return Person.COMPARE_BY_NAME.compare(person1, person2);
              }
          }
          return 0;
     };

     // Sorting method
     public Map<String, List<Person>> sort(Map<String, List<Person>> map, Comparator<? super List<Person>> comparator) {
          return map.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue(comparator))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new));
     }

}

Main.java -> Start code

public class MainApp {

     public static void main(String[] args) {

          Map<String, List<Person>> exampleMap = new HashMap<>();
          List<Person> personList = new ArrayList<>();
          personList.add(new Person("name1", new Date("2022-01-01")));              
          personList.add(new Person("name12", new Date("2022-01-05")));
          personList.add(new Person("name13", new Date("2022-01-03")));
          map.put("2022-01", personList);

          personList.clear();
          personList.add(new Person("name14", new Date("2021-02-01")));              
          personList.add(new Person("name3", new Date("2021-02-05")));
          personList.add(new Person("name4", new Date("2021-02-03")));
          map.put("2021-02", personList);

          Sorter sorter = new Sorter();

          // Example of sorting by date
          map = sorter.sort(exampleMap, Sorter.COMPARATOR_BY_DATE);
          // In this case the sorting works correctly, or rather it sorts the items by date as I expect
         
          // Example of sorting by name
          map = sorter.sort(exampleMap, Sorter.COMPARATOR_BY_NAME);
          // In this case, I don't think sorting works correctly. Sort each list of elements for each key in ascending order. But it doesn't sort the map elements.

          /* I expect to have the following map when sort by date:
             "2021-02": [
               Person("name14", new Date("2021-02-01")),
               Person("name4", new Date("2021-02-03")),
               Person("name3", new Date("2021-02-05"))
             ], 
             "2022-01": [
               Person("name14", new Date("2021-02-01")),
               Person("name13", new Date("2022-01-03")),
               Person("name12", new Date("2022-01-05"))
             ]
             
     }

}

CodePudding user response:

First, let's reiterate: a HashMap is unordered so you need something else. Your Sorter.sort() method actually collects the values into a LinkedHashMap which provides an iteration order based on insert order and would be ok for your use case. Just to be clear (also for the sake of others): this doesn't sort the map itself but creates a new LinkedHashMap.

Now to your comparators: if you want to compare 2 lists you probably want to compare elements at equal indices. Thus your comparator needs to be something like this:

Comparator<List<Person>> = (l1, l2) -> {
   Iterator<Person> itr1 = l1.iterator();
   Iterator<Person> itr2 = l2.iterator();

   while( itr1.hasNext() && itr2.hasNext() ) {
     Person p1 = itr1.next();
     Person p2 = itr1.next();

     int result = Person.COMPARE_BY_DATE.compare(p1, p2);
     if( result != 0 ) {
       return result;
     }
   }

   return 0;
};
 

However, the lists might have different lengths as well so you might want to handle that too:

Comparator<List<Person>> = (l1, l2) -> {
   //iterators and loop here

   //after the loop it seems all elements at equal indices are equal too
   //now compare the sizes

   return Integer.compare(l1.size(), l2.size());
}

CodePudding user response:

By changing the type of Map used in my code above, as suggested by @Thomas, in TreeMap<>, I found the solution to the problem as follows:

  1. I first merged all the Person object lists into one list. This was then sorted by the chosen criterion, for example Person.COMPARE_BY_NAME;
  2. I created an algorithm that would re-group the sorted lists, in according to the criteria of my project, in a map. The key of this map corresponds to the concatenation of the month year of the Person object. The algorithm is reported at the bottom of the comment;
  3. I sort the map based on the chosen attribute, for example Sorter.COMPARATOR_BY_NAME;

Di seguito il codice è come segue:

Merge all List<Person> in one -> main or somewhere before the Map was created

    ...
    //
    List<Person> newPersonList = new ArrayList<>();
    newPersonList.addAll(oldPersonList1);
    newPersonList.addAll(oldPersonList2);
    ...

Main or somewhere before the Map was created

    ...
    groupList(Person.COMPARE_BY_NAME, Sorter.COMPARATOR_BY_NAME);
    ...

GroupPerson -> method to group the merged List<Person> in a TreeMap<String, List<Person>>

    public Map<String, List<Person>> groupList(final Comparator<? super Person> itemComparator, final Comparator<? super List<Person>> listComparator)
         
         // Sort Person list by comparator before create TreeSet
         newPersonList.sort(itemComparator);

         Map<String, List<Person>> personMapGrouped = new TreeMap<>();
         
         // Here, create a Map of list
         for (Person person: newPersonList) {
             final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy MM", Locale.getDefault());
             final String groupKey = dateFormat.format(person.getDateOfBirth());

             if (personMapGrouped.containsKey(groupKey)) {
                // The key is already in the TreeMap; add the Person object against the existing key.
                final List<Person> personListGrouped = personMapGrouped.get(groupKey);
                if (personListGrouped!= null) {
                   personListGrouped.add(person);
                }
             } else {
                // The key is not there in the TreeMap; create a new key-value pair
                final List<Person> personListGrouped = new ArrayList<>();
                personListGrouped.add(person);
                personMapGrouped.put(groupKey, personListGrouped);
             }
         }
         // Here sort the Map by params passed
         final TabPersonSorter sorter = new TabPersonSorter();
         personMapGrouped = sorter.sort(personMapGrouped, listComparator);
    }

In this case, using the lists created in the main above, the results obtained are:

    "List<Person> mergedList": [
        Person("name1", new Date("2022-01-01")),
        Person("name3", new Date("2021-02-05")),
        Person("name4", new Date("2021-02-03")),
        Person("name12", new Date("2022-01-05")),
        Person("name13", new Date("2022-01-03")),
        Person("name14", new Date("2021-02-01"))
    ]

    "Map<String, List<Person>> mergedMap": {
        "2022-01": [
            Person("name1", new Date("2022-01-01")),
            Person("name12", new Date("2022-01-05")),
            Person("name13", new Date("2022-01-03"))
        ], 
        "2021-02": [
            Person("name3", new Date("2021-02-05")),
            Person("name4", new Date("2021-02-03"))
        ],
        "2022-02": [
            Person("name14", new Date("2021-02-01"))
        ]
    } 

Obviously if the grouping in the map were not bound by such a restrictive date as only year month, the sorting would have the desired effect in distinct groups. In fact, in the case of the sorting by date, this is respected very well.

  •  Tags:  
  • Related