将值聚合并在同一个Java流中转换为单一类型

huangapple go评论73阅读模式
英文:

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&lt;Pair&lt;Boolean, String&gt;&gt;.

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&lt;Pair&lt;Boolean, String&gt;&gt;, the output should be Optional&lt;Signal&gt; where Signal is like:

class Signal {
   public boolean exposure;

   public Set&lt;String&gt; alarms;

   // constructor and getters (can add anything to this class, it&#39;s just a bag)
}

This is what I currently have that works:

// Seed::hadExposure yields Optional&lt;Pair&lt;Boolean, String&gt;&gt; where Pair have key/value or left/right
public Optional&lt;Signal&gt; 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 -&gt;
        new Signal(
            exposure.getLeft(),
            exposure.getRight() == null
                ? Collections.emptySet()
                : Collections.singleton(exposure.getRight())))
    .reduce(
        new Signal(false, new HashSet&lt;&gt;()),
        (leftSig, rightSig) -&gt; {
            HashSet&lt;String&gt; alarms = new HashSet&lt;&gt;();
            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) =&gt; 
  (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&lt;Pair&lt;Boolean, String&gt;&gt; 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.

huangapple
  • 本文由 发表于 2020年9月25日 05:20:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/64054577.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定