我按以下格式列出了该列表:
List<MyObject> myObj = Arrays.asList(
new MyObject(time="2017-05-09T15:37:51.896+00:00", name="123", status=200)
new MyObject(time="2017-05-09T15:37:57.090+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:37:59.733+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:39:57.883+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:40:00.862+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:40:04.659+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:40:05.114+00:00", name="ABC", status=500)
new MyObject(time="2017-05-09T15:45:58.796+00:00", name="XYZ", status=200)
new MyObject(time="2017-05-09T15:46:00.562+00:00", name="XYZ", status=200)
new MyObject(time="2017-05-09T15:48:04.144+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:48:04.364+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:48:04.750+00:00", name="ABC", status=200)
new MyObject(time="2017-05-09T15:48:07.052+00:00", name="XYZ", status=202)
);
我想迭代它并以1米间隔执行一些分组,并实现如下:
ABC
-> 15:37
-> 200 -> 2
-> 202 -> 0
-> 500 -> 0
-> 15:38
-> 200 -> 0
-> 202 -> 0
-> 500 -> 0
-> 15:39
-> 200 -> 1
-> 202 -> 0
-> 500 -> 0
-> 15:40
-> 200 -> 2
-> 202 -> 0
-> 500 -> 1
到目前为止,我所尝试的是:
myObj.stream()
.collect(Collectors.groupingBy(MyObject::getName,
Collectors.groupingBy(MyObject::getTime)));
但这实际上是按名称分组,然后按时间分组。但是我希望有1m的时间间隔,然后按状态分组。
在这里需要帮助,因为我是java中的溪流和lambdas的新手。
编辑:请注意,getTime
会返回String
,而不是Date
。
答案 0 :(得分:3)
这可以通过以下方式实现:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
Map<String, Map<String, Map<Integer, Integer>>> collect = myObj.stream()
.collect(Collectors.groupingBy(MyObject::getName, TreeMap::new,
Collectors.groupingBy(
myObject -> ZonedDateTime.parse(myObject.getTime()).format(formatter),
TreeMap::new,
Collectors.groupingBy(MyObject::getStatus, TreeMap::new,
Collectors.summingInt(i -> 1)
)
)
));
结果:
{
123={
15:37={200=1}
},
ABC={
15:37={200=2}, 15:39={200=1}, 15:40={200=2, 500=1}, 15:48={200=3}
},
XYZ={
15:45={200=1}, 15:46={200=1}, 15:48={202=1}
}
}
答案 1 :(得分:0)
据我所知,您需要“空”条目,包括没有相关名称对象的分钟,以及在该分钟内未出现的状态。 Streams非常适合处理流中的元素,但不擅长发明那些尚未存在的元素。
我的建议是,您使用流构建元素来构建地图结构,然后填写缺少的空条目。第一部分非常像Flown’s answer。
首先,我建议除了标准的外,我还能为你的MyObject
课程增加一个额外的吸气剂:
public LocalTime getWholeMinute() {
return ZonedDateTime.parse(time)
.withZoneSameInstant(ZoneOffset.UTC)
.toLocalTime()
.truncatedTo(ChronoUnit.MINUTES);
}
这将返回整个时间段。如果您需要区分几天的时间(并且您可以随后将其格式化以仅打印时间),则该方法可以返回LocalDateTime
。如果您无法修改MyObject
,则可以改为声明采用MyObject
的静态方法,提取时间字符串,执行上述计算并返回LocalTime
或LocalDateTime
。接下来,任何一种建议的方法都会派上用场:
final int[] statuses = { 200, 202, 500 };
if (! myObj.isEmpty()) {
LocalTime minTime = myObj.stream()
.map(MyObject::getWholeMinute)
.min(Comparator.naturalOrder())
.get();
LocalTime maxTime = myObj.stream()
.map(MyObject::getWholeMinute)
.max(Comparator.naturalOrder())
.get();
Map<String, Map<LocalTime, Map<Integer, Long>>> counts = myObj.stream()
.collect(Collectors.groupingBy(MyObject::getName,
Collectors.groupingBy(MyObject::getWholeMinute,
Collectors.groupingBy(MyObject::getStatus,
Collectors.counting()))));
for (Map.Entry<String, Map<LocalTime, Map<Integer, Long>>> outerEntry :
counts.entrySet()) {
Map<LocalTime, Map<Integer, Long>> middleMap = outerEntry.getValue();
LocalTime currentMinute = minTime;
while (! currentMinute.isAfter(maxTime)) {
// fill in missing map
Map<Integer, Long> innerMap
= middleMap.computeIfAbsent(currentMinute, t -> new HashMap<>(4));
// fill in missing counts
for (int status : statuses) {
innerMap.putIfAbsent(status, 0L);
}
currentMinute = currentMinute.plusMinutes(1);
}
}
// ...
}
我想到的另一种方法是首先创建填充零计数的地图结构,然后迭代列表并增加遇到的每个对象的相关计数。我认为它只需要更多的代码行,并且可能更清晰,因为它不会使用许多嵌套的收集器。