Я провел много исследований по этому вопросу, но не нашел способа отсортировать карту списков настраиваемых объектов (Map<String, List<CustomObj>>), основываясь на сравнении атрибутов CustomObj (как SORT_BY_NAME, SORT_BY_DATE и т. д.).

Мотивирующий пример моего вопроса:

  • У меня есть пользовательский объект: Person (с атрибутом Name, DateOfBith и т. д.);
  • У меня есть Map объекта Person List как: Map<String, List<Person>>. Ключ карты — это String, используемый для других целей;
  • Я хотел бы создать компаратор и метод сортировки, который сортирует карту в порядке возрастания на основе сравнений между атрибутами объекта Person (имя, дата и т.д..)

Для простоты я сообщаю реальный код, но адаптированный к упрощенному случаю объекта Person, потому что он уже представляет концепцию объекта.

Person.java -> Пользовательский объект

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 -> Объект сортировщика

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 -> Код запуска

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"))
             ]
             
     }

}
2
Giuseppe Mondelli 26 Янв 2022 в 12:24
4
Примечание. Вы не можете сортировать HashMap. Это одно из его основных свойств, т. е. в нем нет никакого порядка. Если вам нужно понятие порядка, используйте либо TreeMap, который сортирует по ключу, либо LinkedHashMap, который упорядочивает по порядку вставки.
 – 
Thomas
26 Янв 2022 в 12:32
Второе примечание: return Person.COMPARE_BY_DATE.compare(person1, person2); и т. д. в ваших компараторах вернут 0, если первые 2 человека имеют значение (дата в данном случае). Вы, вероятно, захотите продолжить сравнение, пока не найдете результат, отличный от 0, или не дойдете до конца любого списка.
 – 
Thomas
26 Янв 2022 в 12:34
Вы можете использовать entrySet(), если хотите отсортировать. Например, stackoverflow. com/questions/29567575/…, но это не сортирует резервный HashMap. Просто к вашему сведению
 – 
SMA
26 Янв 2022 в 12:34
Спасибо @Томас. Что касается первой ноты, то это моя основная ошибка. Для второго примечания: намерение состоит в том, чтобы продолжить сортировку всех элементов списков. В частности, вся карта должна быть отсортирована на основе дат каждого элемента списка, а не ключей.
 – 
Giuseppe Mondelli
26 Янв 2022 в 12:42
Во-первых: ваше требование неясно. Сортировка списка лиц по некоторым признакам понятна. Но что вы подразумеваете под сортировкой карты со списком лиц? Во-вторых: в Java есть SortedMaps (например, TreeMap). Но согласно документации SortedMap упорядочивается только по ключу, а не по значению. Таким образом, в контексте коллекций Java сортировка карты по значениям невозможна. Конечно, вы можете реализовать свою собственную карту, упорядоченную по значению, но вы должны сначала определить, что именно это означает.
 – 
vanje
26 Янв 2022 в 12:45

2 ответа

Во-первых, давайте еще раз повторим: HashMap не упорядочен, поэтому вам нужно что-то еще. Ваш метод Sorter.sort() фактически собирает значения в LinkedHashMap, который обеспечивает порядок итерации на основе порядка вставки и подходит для вашего варианта использования. Просто для ясности (также для других): это не сортирует саму карту, а создает новый LinkedHashMap.

Теперь к вашим компараторам: если вы хотите сравнить 2 списка, вы, вероятно, захотите сравнить элементы с одинаковыми индексами. Таким образом, ваш компаратор должен быть примерно таким:

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;
};
 

Однако списки также могут иметь разную длину, поэтому вы можете захотеть обработать и это:

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());
}
0
Thomas 26 Янв 2022 в 12:43

Изменив тип Map, используемый в моем коде выше, как это было предложено @Thomas, в TreeMap<>, я нашел решение проблемы следующим образом:

  1. Сначала я объединил все списки объектов Person в один список. Затем это было отсортировано по выбранному критерию, например Person.COMPARE_BY_NAME;
  2. Я создал алгоритм, который перегруппирует отсортированные списки в соответствии с критериями моего проекта на карте. Ключ этой карты соответствует соединению месяца и года объекта Person. Алгоритм сообщается внизу комментария;
  3. Я сортирую карту по выбранному атрибуту, например Sorter.COMPARATOR_BY_NAME;

Di seguito il codice è come segue:

Объединить все List<Person> в один -> основной или где-то до создания карты

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

Главная или где-то до того, как Карта была создана

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

GroupPerson -> метод для группировки объединенных List<Person> в 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);
    }

В этом случае, используя списки, созданные в основном выше, получаются следующие результаты:

    "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"))
        ]
    } 

Очевидно, что если бы группировка на карте не была связана такой ограничительной датой, как только год + месяц, сортировка по отдельным группам имела бы желаемый эффект. На самом деле, в случае сортировки по дате это соблюдается очень хорошо.

0
Giuseppe Mondelli 26 Янв 2022 в 14:46