英文:
Aggregate values and convert into single type within the same Java stream
问题
// Seed::hadExposure yields Optional<Pair<Boolean, String>> where Pair have key/value or left/right
public Optional<Signal> withExposure() {
// Check if any Seed had exposure
boolean anyExposure = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.anyMatch(Pair::getLeft);
// Collect alarms and filter out null values
Set<String> alarms = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.map(Pair::getRight)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// Create and return Optional<Signal>
return anyExposure ? Optional.of(new Signal(true, alarms)) : Optional.empty();
}
注意:在代码的这部分中,我已经为您提供了翻译后的内容。如果您还有其他需要翻译的内容或问题,请随时提问。
英文:
I have a class with a collection of Seed
elements. One of the method's return type of Seed
is Optional<Pair<Boolean, String>>
.
I'm trying to loop over all seeds
, find if any boolean
value is true
and at the same time, create a set with all the String
values. For instance, my input is in the form Optional<Pair<Boolean, String>>
, the output should be Optional<Signal>
where Signal
is like:
class Signal {
public boolean exposure;
public Set<String> alarms;
// constructor and getters (can add anything to this class, it's just a bag)
}
This is what I currently have that works:
// 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));
}
Now I have time to make it better because Seed::hadExposure
could become and expensive call, so I was trying to see if I could make all of this with only one pass. I've tried (some suggestions from previous questions) with reduce
, using collectors (Collectors.collectingAndThen
, Collectors.partitioningBy
, etc.), but nothing so far.
答案1
得分: 1
在单个stream()
表达式中实现这一点是可能的,使用map
将非空曝光转换为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
,并将新的警报添加到累积警报中。
在从头开始设计支持函数式编程的语言(如Scala或Haskell)中,你会有一个Set
数据类型,它允许你高效地创建一个与现有集合相同但具有添加元素的新集合,因此不会有效率问题:
filteredSeeds.foldLeft((false, Set[String]())) { (result, exposure) =>
(result._1 || exposure.getLeft, result._2 + exposure.getRight)
}
但是Java没有像这样的东西内置在其中。
你可以为结果创建一个单独的Set
,并在流的reduce
表达式中对其进行变异,但有些人认为这是不良风格,因为你会将函数式范 paradigm(在流上进行映射/规约)与过程范 paradigm(变异集合)混合在一起。
个人而言,在Java中,在这种情况下我会放弃函数式方法,而是使用for
循环。这将是更少的代码,更高的效率,并且在我看来更清晰。
如果有足够的空间来存储中间结果,你可以做类似这样的事情:
List<Pair<Boolean, String>> exposures =
seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.collect(Collectors.toList());
然后你只需要为输入列表中的每个项目调用一次昂贵的Seed::hadExposure
方法。
英文:
It's possible to do this in a single stream()
expression using map
to convert the non-empty exposure to a Signal
and then a reduce
to combine the signals:
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);
});
However, if you have a lot of alarms it would be expensive because it creates a new Set
and adds the new alarms to the accumulated alarms for each exposure in the input.
In a language that was designed from the ground-up to support functional programming, like Scala or Haskell, you'd have a Set
data type that would let you efficiently create a new set that's identical to an existing set but with an added element, so there'd be no efficiency worries:
filteredSeeds.foldLeft((false, Set[String]())) { (result, exposure) =>
(result._1 || exposure.getLeft, result._2 + exposure.getRight)
}
But Java doesn't come with anything like that out of the box.
You could create just a single Set
for the result and mutate it in your stream's reduce
expression, but some would regard that as poor style because you'd be mixing a functional paradigm (map/reduce over a stream) with a procedural one (mutating a set).
Personally, in Java, I'd just ditch the functional approach and use a for
loop in this case. It'll be less code, more efficient, and IMO clearer.
If you have enough space to store an intermediate result, you could do something like:
List<Pair<Boolean, String>> exposures =
seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.collect(Collectors.toList());
Then you'd only be calling the expensive Seed::hadExposure
method once per item in the input list.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论