Сортировка и суб-листинг из выражения лямбда
У меня есть список массивов со следующими элементами.
List<Record> list = new ArrayList<>();
list.add(new Record(3, "32"));
list.add(new Record(4, "42"));
list.add(new Record(1, "12"));
list.add(new Record(1, "11"));
list.add(new Record(2, "22"));
list.add(new Record(5, "52"));
list.add(new Record(5, "53"));
list.add(new Record(5, "51"));
Запись - это простое POJO с идентификатором и именем
Я хочу сделать это в списке.
-
Создайте такую карту, как Map<Integer, List<Record>>
, у которой ключ - это идентификатор id и более тонкий ключ в качестве списка. Я сделал это как ниже.
Map<Integer, List<Record>> map = list.stream()
.collect(Collectors.groupingBy(Record::getId, HashMap::new, Collectors.toList()));
-
Теперь я хочу отсортировать список по имени и подкатегорию с предоставленным лимитом внутри карты
map.forEach((k, v) -> v.stream().sorted(Comparator.comparing(Record::getName)));
map.forEach((k, v) -> map.put(k, v.subList(0, Math.min(**limit**, v.size()))));
Я пробовал, как указано выше, и выглядит так, как будто это не очень хорошо. Может ли кто-нибудь предложить лучший способ?
Ответы
Ответ 1
Вы можете использовать Java 8 collectingAndThen метод:
Map<Integer, List<Record>> map = list.stream()
.collect(Collectors.groupingBy(
Record::getId,
Collectors.collectingAndThen(
Collectors.toList(),
records -> records.stream()
.sorted(Comparator.comparing(Record::getName))
.limit(limit)
.collect(Collectors.toList()))));
Ответ 2
Вы можете использовать Collectors.collectingAndThen
:
Map<Integer, List<Record>> result = list.stream()
.collect(Collectors.groupingBy(
Record::getId,
Collectors.collectingAndThen(
Collectors.toCollection(ArrayList::new),
v -> {
v.sort(Comparator.comparing(Record::getName));
return v.subList(0, Math.min(LIMIT, v.size()));
})));
Это решение позволяет избежать создания нового потока для каждой группы списков.
Как указано в этом ответе, используя Collectors.toCollection(ArrayList::new)
, мы гарантируем, что список изменен, поэтому мы можем впоследствии отсортировать его на месте.
Ответ 3
Вы можете использовать
Map<Integer, List<Record>> map = list.stream()
.collect(Collectors.groupingBy(Record::getId,Collectors.toCollection(ArrayList::new)));
map.values().forEach(l -> {
list.sort(Comparator.comparing(Record::getName));
l.subList(limit, l.size()).clear();
});
Используя Collectors.toCollection(ArrayList::new)
, мы гарантируем, что список результатов будет изменен. Затем мы сортируем список на месте и удаляем ненужные значения. Вместо того, чтобы создавать подсписку, содержащую элементы, которые мы хотим (которые будут содержать ссылку на полный список), мы строим подсписку элемента, который нам не нужен, и clear()
it, чтобы эффективно удалить этот элемент из исходного списка.
Вы также можете записать его как один оператор:
Map<Integer, List<Record>> map = list.stream()
.collect(Collectors.groupingBy(Record::getId,
Collectors.collectingAndThen(
Collectors.toCollection(ArrayList::new),
l -> {
list.sort(Comparator.comparing(Record::getName));
l.subList(limit, l.size()).clear();
l.trimToSize();
return l;
})));
в качестве бонуса, я также добавил l.trimToSize();
, который направляет ArrayList
на использование меньшего массива, если предыдущий .subList(limit, l.size()).clear()
удалил много элементов. Поскольку это может означать операцию копирования, это компромисс между процессорным временем и памятью здесь. Поэтому, если результат будет использоваться только через довольно короткое время, вы не будете использовать trimToSize()
.
Операция становится еще более простой (и потенциально более эффективной) при использовании StreamEx:
Map<Integer, List<Record>> map = list.stream()
.collect(Collectors.groupingBy(Record::getId,
MoreCollectors.least(Comparator.comparing(Record::getName), limit)));
Ответ 4
list.stream()
.collect(Collectors.groupingBy(
Record::getId,
Collectors.collectingAndThen(
Collectors.toList(),
x -> x.stream()
.sorted(Comparator.comparing(Record::getName))
.limit(limit)
.collect(Collectors.toList()))));
Ответ 5
Вы можете просто сортировать, прежде чем собирать элементы на карте. Для предельного бита вы можете использовать collectingAndThen
для последующей обработки списка и stream.limit
его.
Map<Integer, List<Record>> map = list.stream()
.sorted(Comparator.comparing(Record::getName))
.collect(Collectors.groupingBy(Record::getId,
Collectors.collectingAndThen(Collectors.toList(),
l -> l.stream().limit(limit).collect(Collectors.toList()))));
С limit = 2
это приводит к
{1=[Record(id=1, name=11), Record(id=1, name=12)],
2=[Record(id=2, name=22)],
3=[Record(id=3, name=32)],
4=[Record(id=4, name=42)],
5=[Record(id=5, name=51), Record(id=5, name=52)]}