У меня есть этот список данных, которые выглядят так:
{id, datastring}
{1,"a:1|b:2|d:3"}
{2,"a:2|c:2|c:4"}
{3,"a:2|bb:2|a:3"}
{4,"a:3|e:2|ff:3"}
Мне нужно сделать такие операции, как среднее или найти все id, для которых элемент в строке меньше определенного значения.
Вот несколько примеров;
Сред
{a,2}{b,2}{bb,2}{c,3}{d,3}{e,2}{ff,3}
Найти все id, где c <4
{2}
Найти все id, где a <3
{1,2,3}
Будет ли это хорошим использованием stream() и filter()?
Я знаю, что у вас есть ответ, но вот мои версии тоже:
Map<String, Double> result = list.stream()
.map(Data::getElements)
.flatMap((Multimap<String, Integer> map) -> {
return map.entries().stream();
})
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.averagingInt((Entry<String, Integer> token) -> {
return token.getValue();
})));
System.out.println(result);
List<Integer> result2 = list.stream()
.filter((Data data) -> {
return data.getElements().get("c").stream().anyMatch(i -> i < 4);
})
.map(Data::getId)
.collect(Collectors.toList());
System.out.println(result2);
Да, вы можете использовать потоковые операции для этого, но я бы предложил создать класс для этих данных, чтобы каждая строка соответствовала одному конкретному экземпляру. Это облегчит вашу жизнь ИМО.
class Data {
private int id;
private Map<String, List<Integer>> map;
....
}
Тем не менее, давайте взглянем на то, как вы можете это реализовать. Во-первых, найти всю реализацию:
public static Set<Integer> ids(List<Data> list, String value, Predicate<Integer> boundPredicate) {
return list.stream()
.filter(d -> d.getMap().containsKey(value))
.filter(d -> d.getMap().get(value).stream().anyMatch(boundPredicate))
.map(d -> d.getId())
.collect(toSet());
}
Это просто читать. Вы получаете Stream<Data>
из списка. Затем вы применяете фильтр, чтобы вы получали экземпляры, которые имеют значение, указанное на карте, и что есть значение, которое удовлетворяет предикату, который вы даете. Затем вы сопоставляете каждый экземпляр с соответствующим идентификатором и собираете результирующий поток в наборе.
Пример вызова:
Set<Integer> set = ids(list, "a", value -> value < 3);
который выводит:
[1, 2, 3]
Средний запрос был немного сложнее. Я закончил с другой реализацией, вы, наконец, получили Map<String, IntSummaryStatistics>
в конце (которая содержит среднее значение), а также другую информацию.
Map<String, IntSummaryStatistics> stats = list.stream()
.flatMap(d -> d.getMap().entrySet().stream())
.collect(toMap(Map.Entry::getKey,
e -> e.getValue().stream().mapToInt(i -> i).summaryStatistics(),
(i1, i2) -> {i1.combine(i2); return i1;}));
Сначала вы получаете Stream<Data>
, затем вы flatMap
каждый набор flatMap
каждой карты, чтобы иметь Stream<Entry<String, List<Integer>>
. Теперь вы собираете этот поток в карту, для которой каждый ключ сопоставляется ключом ввода, и каждый List<Integer>
сопоставляется с соответствующим значением IntSummaryStatistics
. Если у вас есть два одинаковых ключа, вы комбинируете их соответствующие значения IntSummaryStatistics
.
Учитывая набор данных, вы получаете Map<String, IntSummaryStatistics>
ff => IntSummaryStatistics{count=1, sum=3, min=3, average=3.000000, max=3}
bb => IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2}
a => IntSummaryStatistics{count=5, sum=11, min=1, average=2.200000, max=3}
b => IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2}
c => IntSummaryStatistics{count=2, sum=6, min=2, average=3.000000, max=4}
d => IntSummaryStatistics{count=1, sum=3, min=3, average=3.000000, max=3}
e => IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2}
из которого вы можете легко захватить среднее.
Здесь полный рабочий пример, реализация, безусловно, может быть улучшена.
getOrDefault
вместо двух filter
: stream().filter(d -> d.getMap().getOrDefault(id, emptyList()).stream().anyMatch(predicate)).map(...)