英文:
Performing multiple reduction operations using Streams
问题
我试图将对象列表流式传输,根据若干字段属性的分组,聚合整数最小值和最大值。
public class OccupancyEntry {
private final int entryID;
private final String attribute1;
private final String attribute2;
private final String attribute3;
private final int min;
private final int max;
public OccupancyEntry(int entryID, String attribute1, String attribute2, String attribute3, int min, int max) {
this.entryID = entryID;
this.attribute1 = attribute1;
this.attribute2 = attribute2;
this.attribute3 = attribute3;
this.min = min;
this.max = max;
}
}
public class OccupancyAggregated {
private final LocalDate date;
private final String attribute1;
private final String attribute2;
private final String attribute3;
private final int min;
private final int max;
public OccupancyAggregated(LocalDate date, String attribute1, String attribute2, String attribute3, int min, int max) {
this.date = date;
this.attribute1 = attribute1;
this.attribute2 = attribute2;
this.attribute3 = attribute3;
this.min = min; //这些是求和后的值
this.max = max; //这些是求和后的值
}
}
OccupancyEntry entry1 = new OccupancyEntry(1, "1", "2", "3", 1, 10);
OccupancyEntry entry2 = new OccupancyEntry(1, "1", "2", "3", 1, 10);
OccupancyEntry entry3 = new OccupancyEntry(1, "A", "B", "C", 1, 10);
ArrayList<OccupancyEntry> occupancyEntries = new ArrayList<>();
occupancyEntries.add(entry1);
occupancyEntries.add(entry2);
occupancyEntries.add(entry3);
LocalDate date = LocalDate.now();
ArrayList<OccupancyAggregated> aggregated = new ArrayList<>();
Map<List<String>, List<OccupancyEntry>> collect = occupancyEntries.stream()
.collect(groupingBy(x -> Arrays.asList(x.getAttribute1(), x.getAttribute2(), x.getAttribute3())));
ArrayList<OccupancyAggregated> aggregated = new ArrayList<>();
英文:
I'm attempting to Stream a list of Objects to aggregate the int min and int max based on the grouping of several Field attributes.
public class OccupancyEntry {
private final int entryID;
private final String attribute1;
private final String attribute2;
private final String attribute3;
private final int min;
private final int max;
public OccupancyEntry(int entryID, String attribute1, String attribute2, String attribute3, int min, int max) {
this.entryID = entryID;
this.attribute1 = attribute1;
this.attribute2 = attribute2;
this.attribute3 = attribute3;
this.min = min;
this.max = max;
}
}
I'd like to map the above Objects to a a list of the following:
public class OccupancyAggregated {
private final LocalDate date;
private final String attribute1;
private final String attribute2;
private final String attribute3;
private final int min;
private final int max;
public OccupancyAggregated(LocalDate date, String attribute1, String attribute2, String attribute3, int min, int max) {
this.date = date;
this.attribute1 = attribute1;
this.attribute2 = attribute2;
this.attribute3 = attribute3;
this.min = min; //these are summed values
this.max = max; //these are summed values
}
}
My attempt so far. Through the help of this answer I've been able to group the entries by a set of fields, but using this method I can no longer get the mapping and reducing functions to work.
OccupancyEntry entry1 = new OccupancyEntry(1, "1", "2", "3", 1, 10);
OccupancyEntry entry2 = new OccupancyEntry(1, "1", "2", "3", 1, 10);
OccupancyEntry entry3 = new OccupancyEntry(1, "A", "B", "C", 1, 10);
ArrayList<OccupancyEntry> occupancyEntries = new ArrayList<>();
occupancyEntries.add(entry1);
occupancyEntries.add(entry2);
occupancyEntries.add(entry3);
LocalDate date = LocalDate.now();
ArrayList<OccupancyAggregated> aggregated = new ArrayList<>();
Map<List<String>, List<OccupancyEntry>> collect = occupancyEntries.stream()
.collect(groupingBy(x -> Arrays.asList(x.getAttribute1(), x.getAttribute2(), x.getAttribute3())));
The sought after output. Where the min and max fields are reduced from from the entirety of the grouped OccupancyEntry list.
ArrayList<OccupancyAggregated> aggregated = new ArrayList<>();
My previous attempts have consisted out of creating multiple streams which reduce either the min field or the max field based on String concatenated attribute fields.
final Map<String, Integer> minOccupancy = occupancyEntries.stream()
.collect((groupingBy(OccupancyEntry::getGroupedAttributes,
mapping(OccupancyEntry::getMin,
reducing(0, Integer::sum)))));
final Map<String, Integer> maxOccupancy = occupancyEntries.stream()
.collect((groupingBy(OccupancyEntry::getGroupedAttributes,
mapping(OccupancyEntry::getMax,
reducing(0, Integer::sum)))));
Afterwards I'd for loop through one of the maps and then start creating a new list with OccupancyAggregated objects and look up the values in both maps to construct it. This seemed much too convoluted.
I've also been trying to perform the reduce operation in one pass using this answer but I'm not sure how to get it to map to the new Class type properly.
occupancyEntries.stream()
.reduce((x, y) -> new OccupancyAggregated(
date,
x.getAttribute1(),
x.getAttribute2(),
x.getAttribute3(),
x.getMin() + y.getMin(),
x.getMax() + y.getMax()
))
.orElse(new OccupancyAggregated(date, "no data", "no data", "no data", 0, 0));
答案1
得分: 0
这是一个非常常见的请求,使用流来解决这个问题会比较复杂,因为聚合中没有"空"或"标识"实例的概念。我更喜欢使用半命令式的方法,使用循环和Map.merge()
:
Map<List<String>, OccupancyAggregated> grouped = new HashMap<>();
for (OccupancyEntry e : occupancyEntries) {
grouped.merge(Arrays.asList(e.attribute1, e.attribute2, e.attribute3),
OccupancyAggregated.from(e, date),
OccupancyAggregated::merge);
}
List<OccupancyAggregated> aggregated = new ArrayList<>(grouped.values());
其中merge()
方法如下:
public OccupancyAggregated merge(OccupancyAggregated o) {
return new OccupancyAggregated(
date, attribute1, attribute2, attribute3, min + o.min, max + o.max);
}
另外,如果你可以将聚合对象设计成可变的,你可以直接在原地更新min和max:
OccupancyAggregated agg = grouped.computeIfAbsent(
Arrays.asList(e.attribute1, e.attribute2, e.attribute3),
attributes -> OccupancyAggregated.from(attributes, date));
agg.add(e.min, e.max);
出于简洁起见,我省略了一些新方法的实现。如果需要更详细的说明,请告诉我。
英文:
This is a very common request that's messy to solve with streams, because there's no concept of an "empty" or "identity" instance of the aggregate. I prefer the semi-imperative approach using a loop and Map.merge()
:
Map<List<String>, OccupancyAggregated> grouped = new HashMap<>();
for (OccupancyEntry e : occupancyEntries) {
grouped.merge(Arrays.asList(e.attribute1, e.attribute2, e.attribute3),
OccupancyAggregated.from(e, date),
OccupancyAggregated::merge);
}
List<OccupancyAggregated> aggregated = new ArrayList<>(grouped.values());
Where the merge()
method looks like this:
public OccupancyAggregated merge(OccupancyAggregated o) {
return new OccupancyAggregated(
date, attribute1, attribute2, attribute3, min + o.min, max + o.max);
}
Alternatively, if you can make the aggregate mutable, you can update min/max in place:
OccupancyAggregated agg = grouped.computeIfAbsent(
Arrays.asList(e.attribute1, e.attribute2, e.attribute3),
attributes -> OccupancyAggregated.from(attributes, date));
agg.add(e.min, e.max);
I left out some of the new method implementations for the sake of brevity. Let me know if they need more elaboration.
答案2
得分: 0
以下是代码部分的中文翻译:
你可以这样做:
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AggregateToGetMinMax {
public static void main(String[] args) {
List<OccupancyEntry> list = setup();
List<OccupancyAggregated> result = aggregate(list, LocalDate.now());
System.out.println("\nresult: " + result);
}
public static List<OccupancyEntry> setup() {
OccupancyEntry[] entries = {
new OccupancyEntry(1, "1", "2", "3", 1, 10),
new OccupancyEntry(1, "1", "2", "3", 1, 10),
new OccupancyEntry(1, "A", "B", "C", 1, 10)
};
return List.of(entries);
}
public static List<OccupancyAggregated> aggregate(List<OccupancyEntry> occupancyEntries, LocalDate date) {
class MinMax {
int max = 0;
int min = 0;
public MinMax() {
}
public MinMax(OccupancyEntry oe) {
this.max = oe.max;
this.min = oe.min;
}
public MinMax add(MinMax mm) {
this.max += mm.max;
this.min += mm.min;
return this;
}
public String toString() {
return "min: " + min + "; max: " + max;
}
}
Map<String, List<OccupancyEntry>> groupedEntries = occupancyEntries.stream()
.collect(Collectors.groupingBy(OccupancyEntry::getGroupedAttributes));
System.out.println("grouped entries: ");
groupedEntries.entrySet().stream()
.forEach(entry -> System.out.println(entry));
List<OccupancyAggregated> result = groupedEntries.values().stream()
.map(list -> {
MinMax mm = list.stream()
.map(MinMax::new)
.reduce(
new MinMax(),
(MinMax acc, MinMax item) -> acc.add(item));
// 获取第一个元素是可以的,因为列表中的所有条目都至少有一个项目的值。
OccupancyAggregated aggregated = new OccupancyAggregated(date, list.get(0), mm.min, mm.max);
return List.of(aggregated);
})
.collect(Collectors.toList()).stream()
.flatMap(List::stream)
.collect(Collectors.toList());
return result;
}
public static class OccupancyEntry {
private final int entryID;
private final String attribute1, attribute2, attribute3;
public final int min, max;
public OccupancyEntry(int entryID, String attribute1, String attribute2, String attribute3, int min, int max) {
this.entryID = entryID;
this.attribute1 = attribute1;
this.attribute2 = attribute2;
this.attribute3 = attribute3;
this.min = min;
this.max = max;
}
public String getGroupedAttributes() {
return List.of(attribute1, attribute2, attribute3).stream()
.collect(Collectors.joining("|"));
}
@Override
public String toString() {
return "OccupancyEntry [" + entryID + ", " + attribute1 + ", " + attribute2
+ ", " + attribute3 + ", " + min + ", " + max + "]";
}
}
public static class OccupancyAggregated {
final LocalDate date;
String attribute1, attribute2, attribute3;
private int min, max;
public OccupancyAggregated(LocalDate date, OccupancyEntry oe, int min, int max) {
this.date = date;
this.attribute1 = oe.attribute1;
this.attribute2 = oe.attribute2;
this.attribute3 = oe.attribute3;
this.min = min; // 这些是求和值
this.max = max; // 这些是求和值
}
@Override
public String toString() {
return "OccupancyAggregated: ( " +
attribute1 + ", " +
attribute2 + ", " +
attribute3 + ", " +
min + ", " +
max +
" )";
}
}
}
更简洁版本的accumulate()
如下所示:
public static List<OccupancyAggregated> aggregate(List<OccupancyEntry> occupancyEntries, LocalDate date) {
class MinMax {
int max = 0;
int min = 0;
public MinMax() {
}
public MinMax(OccupancyEntry oe) {
this.max = oe.max;
this.min = oe.min;
}
public MinMax add(MinMax mm) {
this.max += mm.max;
this.min += mm.min;
return this;
}
public String toString() {
return "min: " + min + "; max: " + max;
}
}
List<OccupancyAggregated> result = occupancyEntries.stream()
.collect(Collectors.groupingBy(OccupancyEntry::getGroupedAttributes))
.values().stream()
.map(list -> {
MinMax mm = list.stream()
.map(MinMax::new)
.reduce(
new MinMax(),
(MinMax acc, MinMax item) -> acc.add(item));
// 获取第一个元素是可以的,因为列表中的所有条目都至少有一个项目的值。
OccupancyAggregated aggregated = new OccupancyAggregated(date, list.get(0), mm.min, mm.max);
return List.of(aggregated);
})
.collect(Collectors.toList()).stream()
.flatMap(List::stream)
.collect(Collectors.toList());
return result;
}
希望这对你有所帮助!
英文:
You could do this:
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AggregateToGetMinMax {
public static void main(String[] args) {
List<OccupancyEntry> list = setup();
List<OccupancyAggregated> result = aggregate(list, LocalDate.now());
System.out.println("\nresult: " + result);
}
public static List<OccupancyEntry> setup() {
OccupancyEntry[] entries = {
new OccupancyEntry(1, "1", "2", "3", 1, 10),
new OccupancyEntry(1, "1", "2", "3", 1, 10),
new OccupancyEntry(1, "A", "B", "C", 1, 10)
};
return List.of(entries);
}
public static List<OccupancyAggregated> aggregate(List<OccupancyEntry> occupancyEntries, LocalDate date) {
class MinMax {
int max = 0;
int min = 0;
public MinMax() {
}
public MinMax(OccupancyEntry oe) {
this.max = oe.max;
this.min = oe.min;
}
public MinMax add(MinMax mm) {
this.max += mm.max;
this.min += mm.min;
return this;
}
public String toString() {
return "min: " + min + "; max: " + max;
}
}
Map<String, List<OccupancyEntry>> groupedEntries = occupancyEntries.stream()
.collect(Collectors.groupingBy(OccupancyEntry::getGroupedAttributes));
System.out.println("grouped entries: ");
groupedEntries.entrySet().stream()
.forEach(entry -> System.out.println(entry));
List<OccupancyAggregated> result = groupedEntries.values().stream()
.map(list -> {
MinMax mm = list.stream()
.map(MinMax::new)
.reduce(
new MinMax(),
(MinMax acc, MinMax item) -> acc.add(item));
// It's okay to get(0) because all entries with have values with at least one item
// in the list.
OccupancyAggregated aggregated = new OccupancyAggregated(date, list.get(0), mm.min, mm.max);
return List.of(aggregated);
})
.collect(Collectors.toList()).stream()
.flatMap(List::stream)
.collect(Collectors.toList());
return result;
}
public static class OccupancyEntry {
private final int entryID;
private final String attribute1, attribute2, attribute3;
public final int min, max;
public OccupancyEntry(int entryID, String attribute1, String attribute2, String attribute3, int min, int max) {
this.entryID = entryID;
this.attribute1 = attribute1;
this.attribute2 = attribute2;
this.attribute3 = attribute3;
this.min = min;
this.max = max;
}
public String getGroupedAttributes() {
return List.of(attribute1, attribute2, attribute3).stream()
.collect(Collectors.joining("|"));
}
@Override
public String toString() {
return "OccupancyEntry [" + entryID + ", " + attribute1 + ", " + attribute2
+ ", " + attribute3 + ", " + min + ", " + max + "]";
}
}
public static class OccupancyAggregated {
final LocalDate date;
String attribute1, attribute2, attribute3;
private int min, max;
public OccupancyAggregated(LocalDate date, OccupancyEntry oe, int min, int max) {
this.date = date;
this.attribute1 = oe.attribute1;
this.attribute2 = oe.attribute2;
this.attribute3 = oe.attribute3;
this.min = min; // these are summed values
this.max = max; // these are summed values
}
@Override
public String toString() {
return "OccupancyAggregated: ( " +
attribute1 + ", " +
attribute2 + ", " +
attribute3 + ", " +
min + ", " +
max +
" )";
}
}
}
Note that I have separated the grouping and reducing so that one can more clearly focus on the reducing part of the work. Otherwise, building the entire operation straight through might become a bit tedious to mentally parse the important operation.
Output:
grouped entries:
A|B|C=[OccupancyEntry [1, A, B, C, 1, 10]]
1|2|3=[OccupancyEntry [1, 1, 2, 3, 1, 10], OccupancyEntry [1, 1, 2, 3, 1, 10]]
result: [OccupancyAggregated: ( A, B, C, 1, 10 ), OccupancyAggregated: ( 1, 2, 3, 2, 20 )]
The more succinct version of accumulate()
looks like this:
public static List<OccupancyAggregated> aggregate(List<OccupancyEntry> occupancyEntries, LocalDate date) {
class MinMax {
int max = 0;
int min = 0;
public MinMax() {
}
public MinMax(OccupancyEntry oe) {
this.max = oe.max;
this.min = oe.min;
}
public MinMax add(MinMax mm) {
this.max += mm.max;
this.min += mm.min;
return this;
}
public String toString() {
return "min: " + min + "; max: " + max;
}
}
List<OccupancyAggregated> result = occupancyEntries.stream()
.collect(Collectors.groupingBy(OccupancyEntry::getGroupedAttributes))
.values().stream()
.map(list -> {
MinMax mm = list.stream()
.map(MinMax::new)
.reduce(
new MinMax(),
(MinMax acc, MinMax item) -> acc.add(item));
// It's okay to get(0) because all entries with have values with at least one item
// in the list.
OccupancyAggregated aggregated = new OccupancyAggregated(date, list.get(0), mm.min, mm.max);
return List.of(aggregated);
})
.collect(Collectors.toList()).stream()
.flatMap(List::stream)
.collect(Collectors.toList());
return result;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论