Java 8, лямбда: сортировка внутри сгруппированных списков и объединение всех групп в список
на основе следующего ответа:https://stackoverflow.com/a/30202075/8760211
как отсортировать каждую группу по stud_id, а затем вернуть список со всеми студентами в результате группировки по stud_location, а затем сортировки по stud_id)?
было бы здорово иметь это как расширение существующего лямбда-выражения:
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));
мне нужна группировка на основе порядка элементов в исходном списке.
First group: "New York"
Second group: "California"
Third group: "Los Angeles"
1726, "John", "New York"
4321, "Max", "California"
2234, "Andrew", "Los Angeles"
5223, "Michael", "New York"
7765, "Sam", "California"
3442, "Mark", "New York"
в результат будет выглядеть следующим образом:
List<Student> groupedAndSorted = ....
1726, "John", "New York"
3442, "Mark", "New York"
5223, "Michael", "New York"
4321, "Max", "California"
7765, "Sam", "California"
2234, "Andrew", "Los Angeles"
я попробовал следующее:
studlistGrouped.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getValue))
но это не сработает.
9 ответов
Если я правильно понял, вы хотите List<Student>
(не карта), где студенты группируются по их местоположениям и сортируются по идентификаторам внутри групп и где группы также сортируются по номерам, а не по именам расположение. Это возможно, но требует одной группировки и двух сортировок:
//first, use your function to group students
Map<String, List<Student>> studlistGrouped = students.stream()
.collect(Collectors.groupingBy(Student::getLocation, Collectors.toList()));
//then sort groups by minimum id in each of them
List<Student> sorted = studlistGrouped.entrySet().stream()
.sorted(Comparator.comparing(e -> e.getValue().stream().map(Student::getId).min(Comparator.naturalOrder()).orElse(0)))
//and also sort each group before collecting them in one list
.flatMap(e -> e.getValue().stream().sorted(Comparator.comparing(Student::getId))).collect(Collectors.toList());
это произведет следующее:
Student{id='1726', name='John', location='New York'}
Student{id='3442', name='Mark', location='New York'}
Student{id='5223', name='Michael', location='New York'}
Student{id='2234', name='Andrew', location='Los Angeles'}
Student{id='4321', name='Max', location='California'}
Student{id='7765', name='Sam', location='California'}
возможно, это можно сделать более элегантно, предложения приветствуются
EDIT: в то время этот ответ был написано не было упоминания о группировка на основе порядка элементов в исходном списке в вопросе OPs. Таким образом, мое предположение заключалось в сортировке списка и групп по идентификаторам. Для решений, основанных на порядке в исходном списке, см. другие ответы, например, Holgers one
поскольку результат должен быть списком, вы не группируете, а просто сортируете (в смысле изменения порядка в соответствии с определенным правилом). Основным препятствием является то, что вы хотите, чтобы места были заказаны после их первой встречи в первоначальный список.
прямой подход заключается в том, чтобы сначала исправить этот порядок расположения, а затем выполнить одну операцию сортировки:
Map<String,Integer> locationOrder = studlist.stream()
.collect(HashMap::new,
(m,s)->m.putIfAbsent(s.stud_location, m.size()),
(m1,m2)->m2.keySet().forEach(l->m1.putIfAbsent(l, m1.size())));
studlist.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
.thenComparing(s -> s.stud_id));
если вы не можете или не хотите изменять исходный список, вы можете просто использовать копировать:
List<Student> result = new ArrayList<>(studlist);
result.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
.thenComparing(s -> s.stud_id));
это также можно решить с помощью операции группировки, но это не проще:
List<Student> result = studlist.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(s -> s.stud_location,
LinkedHashMap::new, Collectors.toList()),
m -> m.values().stream()
.flatMap(l -> l.stream().sorted(Comparator.comparing(s->s.stud_id)))
.collect(Collectors.toList())));
обратите внимание, что вы должны собирать в LinkedHashMap
для обеспечения сохранения порядка групп.
вы можете добавить одну строку:
studlistGrouped.values().forEach(list -> list.sort(Comparator.comparing(Student::getId)));
или вы можете написать свой собственный коллектор.
Я знаю, какой из них я бы выбрал.
попробуйте сначала сортировать, а затем groupinBy он работает хорошо. Приведенный ниже код сортирует студентов в пределах местоположения.
students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation))
Выход в этом случае
{New York=[1726 John New York, 3442 Mark New York, 5223 Michael New York], Los Angeles=[2234 Andrew Los Angeles], California=[4321 Max California, 7765 Sam California]}
если вы хотите, чтобы места также были отсортированы, используйте фрагмент кода, как показано ниже
students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation, TreeMap::new, Collectors.toList()))
выход в этом случае
{California=[4321 Max California, 7765 Sam California], Los Angeles=[2234 Andrew Los Angeles], New York=[1726 John New York, 3442 Mark New York, 5223 Michael New York]}
класс Student реализует Comparable, а метод compareTo основан на идентификаторе.
во-первых, о сортировке внутри каждую группу. Collectors.groupingBy
есть второй вариант, который позволяет указать коллектор, который используется для создания групп. Указанный вами коллектор может быть тем, который собирает элементы отсортированным способом (например, a TreeSet
), и в качестве завершающей операции преобразует его в отсортированный список. Такой коллектор можно создать с помощью Collectors.collectingAndThen()
.
например, с целыми числами I попробовал:
List<Integer> list = Arrays.asList(9, 2, 43, 6, 5, 3, 87, 56, 87, 67, 77, 22, 23, 1);
System.out.println(
list.stream().collect(Collectors.groupingBy(
i -> i % 3, // classifier
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector
set -> new ArrayList<>(set))))); // finishing operation
выход:
{0=[3, 6, 9, 87], 1=[1, 22, 43, 67], 2=[2, 5, 23, 56, 77]}
Я уверен, что вам удастся перевести это в вашем случае. Возможно, Вам потребуется создать TreeSet
С помощью пользовательского компаратора, чтобы студенты в каждой группе упорядочены так, как вы хотите, или, если студенты сортируются одинаково всегда, сделайте Student
реализовать Comparable
.
второй о сортировке групп. Collectors.groupingBy
по умолчанию создает HashMap
, который не имеет заданного порядка ключей (выше, ключи заказаны правильно случайно). Итак, чтобы заказать ключи, вам нужно использовать Collectors.groupingBy
вариант, который также позволяет создавать карту результатов, которая, к счастью, также существует:
System.out.println(
list.stream().collect(Collectors.groupingBy(
i -> i % 3, // classifier
new TreeMap<>((a, b) -> b.compareTo(a)), // map creator
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector
set -> new ArrayList<>(set))))); // finishing operation
я указал пользовательский компаратор для карты, чтобы показать, что порядок действительно отличается. В результате:
{2=[2, 5, 23, 56, 77], 1=[1, 22, 43, 67], 0=[3, 6, 9, 87]}
теперь, является ли это более читаемым и поддерживаемым, чем старое доброе решение pre-Java 8, это вопрос вкуса...
Если вы просто хотите группировать и сортировать, вам не нужен groupingBy()
коллектор или даже поток вообще. Просто используйте составную сортировку:
studlist.sort(Comparator.comparing(Student::getLocation).thenComparing(Student::getId));
не 100% ясно, ожидаете ли вы Map<String, List<Student>>
или просто List<Student>
, тем не менее, вот оба решения:
импорт:
import static java.util.stream.Collectors.*;
import java.util.*;
import java.util.function.Function;
получение Map<String, List<Student>>
каждая List<Student>
содержит студентов, отсортированных по их идентификаторам.
Map<String, List<Student>> resultSet = studlist.stream()
.collect(groupingBy(Student::getLocation,
mapping(Function.identity(),
collectingAndThen(toList(),
e -> e.stream().sorted(Comparator.comparingInt(Student::getId))
.collect(toList())))));
С другой стороны, если вы хотите получить просто список Student
объекты, отсортированные по заданному свойству, тогда было бы пустой тратой ресурсов для выполнения groupingBy
, sorted
, collect
и как-то уменьшить карту значения в один список. Скорее просто сортируйте объекты Student в списке, предоставляя ключ сортировки, т. е.
studlist.sort(Comparator.comparingInt(Student::getId));
или
studlist.sort(Comparator.comparing(Student::getLocation));
или в зависимости от того, хотите ли вы Сортировать по более чем одному свойству, вы можете сделать что-то вроде shmosel ответ.
вам не нужно, чтобы группы по местоположению, так как вход и выход имеют одинаковый тип данных. Вы можете просто связать несколько компараторов и отсортировать входные данные.
List<Student> groupedAndSorted = studlist.stream()
.sorted(Comparator.comparing(Student::getStudLocation)
.thenComparing(Comparator.comparing(Student::getStudId)))
.thenComparing(Comparator.comparing(Student::getStudName)))
.collect(Collectors.toList());
как offtopic, я бы хотел, чтобы вы изменили структуру данных, чтобы использовать Lombok для автоматического создания геттеров/сеттеров / конструкторов https://projectlombok.org/features/Data . Вы также должны использовать более общее преобразование именования, т. е. удалить префикс атрибутов" stud_".
import lombok.Data;
@Data
class Student {
String id;
String name;
String location;
}
я столкнулся с той же проблемой, и я попробовал все решения, но не смог решить проблему, Затем я попытался следовать пути, и, наконец, я могу исправить свою проблему.
в приведенном выше примере мой
studlist
был уже отсортирован, но все еще его генерация карты в любом порядке, который был основным вопросом.
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));
**studlistGrouped = new TreeMap<String, List<Student>>(studlistGrouped);**
поэтому, используя это решение выше, он отсортировал все ключи на карте. Наша цель получить результат в карту только тогда, почему люди пытаются преобразовать его в список?