在同一 Java 流中聚合值并转换为单一类型
Aggregate values and convert into single type within the same Java stream
我有一个包含 Seed
个元素的集合的 class。 Seed
方法的 return 类型之一是 Optional<Pair<Boolean, String>>
.
我正在尝试遍历所有 seeds
,查找是否有任何 boolean
值是 true
,同时创建一个包含所有 String
值。例如,我的输入格式为 Optional<Pair<Boolean, String>>
,输出应为 Optional<Signal>
,其中 Signal
类似于:
class Signal {
public boolean exposure;
public Set<String> alarms;
// constructor and getters (can add anything to this class, it's just a bag)
}
这是我目前可用的:
// Seed::hadExposure yields Optional<Pair<Boolean, String>> where Pair have key/value or left/right
public Optional<Signal> withExposure() {
if (seeds.stream().map(Seed::hadExposure).flatMap(Optional::stream).findAny().isEmpty()) {
return Optional.empty();
}
final var exposure = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.anyMatch(Pair::getLeft);
final var alarms = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.map(Pair::getRight)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return Optional.of(new Signal(exposure, alarms));
}
现在我有时间让它变得更好,因为 Seed::hadExposure
可能会变得很昂贵,所以我想看看我是否可以只通过一次就完成所有这些。我已经尝试过 reduce
、使用收集器(Collectors.collectingAndThen
、Collectors.partitioningBy
等)(来自之前问题的一些建议),但到目前为止还没有。
可以在单个 stream()
表达式中执行此操作,使用 map
将 non-empty 曝光转换为 Signal
,然后将 reduce
转换为合并信号:
Signal signal = exposures.stream()
.map(exposure ->
new Signal(
exposure.getLeft(),
exposure.getRight() == null
? Collections.emptySet()
: Collections.singleton(exposure.getRight())))
.reduce(
new Signal(false, new HashSet<>()),
(leftSig, rightSig) -> {
HashSet<String> alarms = new HashSet<>();
alarms.addAll(leftSig.alarms);
alarms.addAll(rightSig.alarms);
return new Signal(
leftSig.exposure || rightSig.exposure, alarms);
});
但是,如果您有很多警报,成本会很高,因为它会创建一个新的 Set
并将新警报添加到输入中每次曝光的累积警报中。
在从 ground-up 设计用于支持函数式编程的语言中,例如 Scala 或 Haskell,您将拥有一个 Set
数据类型,可以让您 efficiently create a new set that's identical to an existing set 但是添加了一个元素,所以没有效率上的担忧:
filteredSeeds.foldLeft((false, Set[String]())) { (result, exposure) =>
(result._1 || exposure.getLeft, result._2 + exposure.getRight)
}
但是 Java 没有开箱即用的东西。
您可以只为结果创建一个 Set
并在流的 reduce
表达式中改变它,但有些人会认为这是糟糕的风格,因为您会混合使用函数范式( map/reduce 在流上)与程序流(改变一组)。
就我个人而言,在 Java 中,我只是放弃函数式方法,在这种情况下使用 for
循环。它将更少的代码、更高的效率和更清晰的 IMO。
如果您有足够的 space 来存储中间结果,您可以这样做:
List<Pair<Boolean, String>> exposures =
seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.collect(Collectors.toList());
那么您只需为输入列表中的每个项目调用一次昂贵的 Seed::hadExposure
方法。
我有一个包含 Seed
个元素的集合的 class。 Seed
方法的 return 类型之一是 Optional<Pair<Boolean, String>>
.
我正在尝试遍历所有 seeds
,查找是否有任何 boolean
值是 true
,同时创建一个包含所有 String
值。例如,我的输入格式为 Optional<Pair<Boolean, String>>
,输出应为 Optional<Signal>
,其中 Signal
类似于:
class Signal {
public boolean exposure;
public Set<String> alarms;
// constructor and getters (can add anything to this class, it's just a bag)
}
这是我目前可用的:
// Seed::hadExposure yields Optional<Pair<Boolean, String>> where Pair have key/value or left/right
public Optional<Signal> withExposure() {
if (seeds.stream().map(Seed::hadExposure).flatMap(Optional::stream).findAny().isEmpty()) {
return Optional.empty();
}
final var exposure = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.anyMatch(Pair::getLeft);
final var alarms = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.map(Pair::getRight)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return Optional.of(new Signal(exposure, alarms));
}
现在我有时间让它变得更好,因为 Seed::hadExposure
可能会变得很昂贵,所以我想看看我是否可以只通过一次就完成所有这些。我已经尝试过 reduce
、使用收集器(Collectors.collectingAndThen
、Collectors.partitioningBy
等)(来自之前问题的一些建议),但到目前为止还没有。
可以在单个 stream()
表达式中执行此操作,使用 map
将 non-empty 曝光转换为 Signal
,然后将 reduce
转换为合并信号:
Signal signal = exposures.stream()
.map(exposure ->
new Signal(
exposure.getLeft(),
exposure.getRight() == null
? Collections.emptySet()
: Collections.singleton(exposure.getRight())))
.reduce(
new Signal(false, new HashSet<>()),
(leftSig, rightSig) -> {
HashSet<String> alarms = new HashSet<>();
alarms.addAll(leftSig.alarms);
alarms.addAll(rightSig.alarms);
return new Signal(
leftSig.exposure || rightSig.exposure, alarms);
});
但是,如果您有很多警报,成本会很高,因为它会创建一个新的 Set
并将新警报添加到输入中每次曝光的累积警报中。
在从 ground-up 设计用于支持函数式编程的语言中,例如 Scala 或 Haskell,您将拥有一个 Set
数据类型,可以让您 efficiently create a new set that's identical to an existing set 但是添加了一个元素,所以没有效率上的担忧:
filteredSeeds.foldLeft((false, Set[String]())) { (result, exposure) =>
(result._1 || exposure.getLeft, result._2 + exposure.getRight)
}
但是 Java 没有开箱即用的东西。
您可以只为结果创建一个 Set
并在流的 reduce
表达式中改变它,但有些人会认为这是糟糕的风格,因为您会混合使用函数范式( map/reduce 在流上)与程序流(改变一组)。
就我个人而言,在 Java 中,我只是放弃函数式方法,在这种情况下使用 for
循环。它将更少的代码、更高的效率和更清晰的 IMO。
如果您有足够的 space 来存储中间结果,您可以这样做:
List<Pair<Boolean, String>> exposures =
seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.collect(Collectors.toList());
那么您只需为输入列表中的每个项目调用一次昂贵的 Seed::hadExposure
方法。