Java Streams: объединение двух коллекций в карту
у меня есть две коллекции, список складских идентификаторов и коллекция виджетов. Виджеты существуют на нескольких складах в разных количествах:
List<Long> warehouseIds;
List<Widget> widgets;
вот пример defeinition классов:
public class Widget {
public Collection<Stock> getStocks();
}
public class Stock {
public Long getWarehouseId();
public Integer getQuantity();
}
Я хочу использовать API Streams для создания карты, где идентификатор склада является ключевым, а значение-это список виджетов с наименьшим количеством на определенном складе. Поскольку несколько виджетов могут иметь одинаковое количество, мы возвращаем список.
например, склад 111 и 5 кол-во Виджет A, 5 of Виджет B и 8 of Виджет C.
склад 222 и 0 кол-во Виджет A, 5 of Виджет B и 5 of Виджет C Возвращаемая карта будет иметь следующее записи:
111 => ['WidgetA', 'WidgetB']
222 => ['WidgetA']
запуск настройки карты с ключами кажется довольно простым, но я не знаю, как структурировать уменьшение вниз по течению:
warehouseIds.stream().collect(Collectors.groupingBy(
Function::Identity,
HashMap::new,
???...
Я думаю, что проблема, с которой я сталкиваюсь, заключается в уменьшении виджетов на основе идентификатора склада и не зная, как вернуть коллектор для создания этого списка виджетов. Вот как я в настоящее время получаю список виджетов с наименьшим запасом на определенном складе (представленном by someWarehouseId):
widgets.stream().collect(Collectors.groupingBy(
(Widget w)->
w.getStocks()
//for a specific warehouse
.stream().filter(stock->stock.getWarehouseId()==someWarehouseId)
//Get the quantity of stocks for a widget
.collect(Collectors.summingInt(Stock::getQuantity)),
//Use a tree map so the keys are sorted
TreeMap::new,
//Get the first entry
Collectors.toList())).firstEntry().getValue();
разделение этого на две задачи с использованием forEach в списке складов облегчит эту работу, но мне интересно, смогу ли я сделать это в "однострочном".
1 ответов
чтобы решить эту проблему, нам нужно использовать более правильный подход, чем использование TreeMap для выбора значений, имеющих наименьшие величины.
рассмотрим следующий подход:
- мы
Stream<Widget>наши первые виджеты. Нам нужно будет выполнить некоторую обработку запасов каждого виджета,но нам также нужно будет сохранить виджет. Давайте!--4--> этоStream<Widget>наStream<Map.Entry<Stock, Widget>>: что новый поток будет состоять из каждогоStockчто у нас есть, с тегомWidget. - мы фильтруем эти элементы, чтобы только сохранить
Map.Entry<Stock, Widget>где запас имеетwarehouseId, содержащиеся вwarehouseIdsсписок. - теперь нам нужно сгруппировать этот поток в соответствии с
warehouseIdкаждогоStock. Поэтому мы используемCollectors.groupingBy(classifier, downstream)где классификатор возвращает этоwarehouseId. - нисходящий коллектор собирает элементы, которые классифицируются на один и тот же ключ. В этом случае, для
Map.Entry<Stock, Widget>элементы, которые были классифицируется так жеwarehouseId, нам нужно держать только те где запас имеет самое низкое количество. Для этого нет встроенных коллекторов, давайте использоватьMoreCollectors.minAll(comparator, downstream)С StreamEx библиотека. Если вы предпочитаете не использовать библиотеку, я извлек ее код в этот ответ и буду использовать его. - компаратор просто сравнивает количество каждого запаса в
Map.Entry<Stock, Widget>. Это гарантирует, что мы сохраним элементы с наименьшим количеством для фиксированногоwarehouseId. Нисходящий сборник использован для уменьшения собранных элементов. В этом случае мы хотим только сохранить виджет, поэтому мы используемCollectors.mapping(mapper, downstream)где картограф возвращает виджет изMap.Entry<Stock, Widget>и нижестоящие коллекторы собираются в список сCollectors.toList().
пример кода:
Map<Long, List<Widget>> map =
widgets.stream()
.flatMap(w -> w.getStocks().stream().map(s -> new AbstractMap.SimpleEntry<>(s, w)))
.filter(e -> warehouseIds.contains(e.getKey().getWarehouseId()))
.collect(Collectors.groupingBy(
e -> e.getKey().getWarehouseId(),
minAll(
Comparator.comparingInt(e -> e.getKey().getQuantity()),
Collectors.mapping(e -> e.getValue(), Collectors.toList())
)
));
следующим minAll взыскатель:
public static <T, A, D> Collector<T, ?, D> minAll(Comparator<? super T> comparator, Collector<T, A, D> downstream) {
return maxAll(comparator.reversed(), downstream);
}
public static <T, A, D> Collector<T, ?, D> maxAll(Comparator<? super T> comparator, Collector<? super T, A, D> downstream) {
final class PairBox<U, V> {
public U a;
public V b;
PairBox(U a, V b) {
this.a = a;
this.b = b;
}
}
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BinaryOperator<A> downstreamCombiner = downstream.combiner();
Supplier<PairBox<A, T>> supplier = () -> new PairBox<>(downstreamSupplier.get(), null);
BiConsumer<PairBox<A, T>, T> accumulator = (acc, t) -> {
if (acc.b == null) {
downstreamAccumulator.accept(acc.a, t);
acc.b = t;
} else {
int cmp = comparator.compare(t, acc.b);
if (cmp > 0) {
acc.a = downstreamSupplier.get();
acc.b = t;
}
if (cmp >= 0)
downstreamAccumulator.accept(acc.a, t);
}
};
BinaryOperator<PairBox<A, T>> combiner = (acc1, acc2) -> {
if (acc2.b == null) {
return acc1;
}
if (acc1.b == null) {
return acc2;
}
int cmp = comparator.compare(acc1.b, acc2.b);
if (cmp > 0) {
return acc1;
}
if (cmp < 0) {
return acc2;
}
acc1.a = downstreamCombiner.apply(acc1.a, acc2.a);
return acc1;
};
Function<PairBox<A, T>, D> finisher = acc -> downstream.finisher().apply(acc.a);
return Collector.of(supplier, accumulator, combiner, finisher);
}