Reputation: 45
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:
Person
(with attribute as Name
, DateOfBith
, etc ...);Map
of Person
object List
as: Map<String, List<Person>>
. The map key is a String
used for other purposes;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"))
]
}
}
Upvotes: 3
Views: 1622
Reputation: 45
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:
Person
object lists into one list. This was then sorted by the chosen criterion, for example Person.COMPARE_BY_NAME
;Person
object.
The algorithm is reported at the bottom of the comment;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.
Upvotes: 1
Reputation: 88707
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());
}
Upvotes: 1