我需要解析一个由不同整数组成的字符串,表示某个用户正在或没有看电视的时间段。
我首先分割字符串并将其收集到 ArrayList 中:
final List<String> separated = stream(split(s, "A"))
.map(str -> "A" + str)
.flatMap(str -> stream(split(str, "B")).map(s2 -> s2.startsWith("A") ? s2 : "B" + s2))
collect(Collectors.toList());
棘手的事情来了。我需要将这些字符串转换为具有 from/to 字段的域对象。
因此,为了正确映射它,我的映射函数需要了解前一个元素。所以我做了以下事情:
LocalDateTime temp = initial;
final ArrayList<WatchingPeriod> result = new ArrayList<>();
for (final String s1 : separated) {
final WatchingPeriod period = new WatchingPeriod(temp, temp.plusMinutes(parseLong(substring(s1, 1))),
s1.startsWith("A"));
result.add(period);
temp = period.getTo();
}
return result;
我觉得这是一个巨大的倒退,因为我打破了整个流管道只是为了回到每个人的旧学校。
有什么方法可以在一个流管道中完成整个处理吗?我正在考虑创建一个自定义收集器,它将查看集合中的最后一个元素,并基于此计算正确的 LocalDateTime 对象。
示例:
输入字符串:“A60B80A60”,表示有人看了某东西60分钟,然后停了80分钟,然后又看了60分钟
因此我想获得一个包含对象的列表:
1) 从:0:00,到:1:00,观看:true
2) 从:1:00,到:2:20,观看:假
3) 从:2:20,到:3:20,观看:true
每个对象的计算都需要了解前一个对象
最佳答案
这不是关于连续的对,而是关于收集累积前缀。在函数式编程中,此类操作通常称为 scanLeft
并且它存在于许多函数式语言中,例如 Scala 。不幸的是,它在 Java 8 Stream API 的当前实现中不存在,因此我们只能使用 forEachOrdered
来模拟它。让我们创建一个模型对象:
static class WatchPeriod {
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
final LocalTime start;
final Duration duration;
final boolean watched;
WatchPeriod(LocalTime start, Duration duration, boolean watched) {
this.start = start;
this.duration = duration;
this.watched = watched;
}
// Takes string like "A60" and creates WatchPeriod starting from 00:00
static WatchPeriod forString(String watchPeriod) {
return new WatchPeriod(LocalTime.of(0, 0),
Duration.ofMinutes(Integer.parseInt(watchPeriod.substring(1))),
watchPeriod.startsWith("A"));
}
// Returns new WatchPeriod which start time is adjusted to start
// right after the supplied previous period
WatchPeriod after(WatchPeriod previous) {
return new WatchPeriod(previous.start.plus(previous.duration), duration, watched);
}
@Override
public String toString() {
return "from: "+start.format(FORMATTER)+", to: "+
start.plus(duration).format(FORMATTER)+", watched: "+watched;
}
}
现在我们可以将输入字符串如 "A60B80A60"
拆分为标记 "A60", "B80", "A60"
,将这些标记映射到 WatchPeriod
对象,然后将它们存储到结果列表中:
String input = "A60B80A60";
List<WatchPeriod> result = new ArrayList<>();
Pattern.compile("(?=[AB])").splitAsStream(input)
.map(WatchPeriod::forString)
.forEachOrdered(wp -> result.add(result.isEmpty() ? wp :
wp.after(result.get(result.size()-1))));
result.forEach(System.out::println);
输出为:
from: 00:00, to: 01:00, watched: true
from: 01:00, to: 02:20, watched: false
from: 02:20, to: 03:20, watched: true
如果你不介意使用第三方库,我的免费StreamEx增强 Stream API 添加缺失的 scanLeft
其他功能的操作:
String input = "A60B80A60";
List<WatchPeriod> result = StreamEx.split(input, "(?=[AB])")
.map(WatchPeriod::forString).scanLeft((prev, next) -> next.after(prev));
result.forEach(System.out::println);
结果是一样的。
关于java - 在 Java 8 Stream 上执行操作时如何保留状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33822412/