【问题标题】:Combine allMatch, noneMatch and anyMatch on a single stream将 allMatch、noneMatch 和 anyMatch 组合在一个流上
【发布时间】:2016-12-08 09:57:50
【问题描述】:

我想要以下逻辑:(我知道它不起作用,因为它不止一次地消耗流)。但我不确定如何实现它。

Stream<ByteBuffer> buffers = super.getBuffers().stream();
if (buffers.allMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.noneMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

我该怎么做?

【问题讨论】:

  • super.getBuffers()的结果是什么类型?
  • 是列表

标签: java java-8 java-stream


【解决方案1】:

由于super.getBuffers() 的结果是List&lt;ByteBuffer&gt;,你可以迭代两次。

List<ByteBuffer> buffers = super.getBuffers();
if (buffers.stream().allMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.stream().noneMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

请注意,这仍然不需要在所有情况下遍历所有元素。 allMatch 在遇到不匹配的元素时立即返回,noneMatch 在遇到匹配的元素时立即返回。所以在PARTIALLY_SENT的情况下,有可能不看所有元素就得出结论。

另一种选择是

List<ByteBuffer> buffers = super.getBuffers();
if(buffers.isEmpty()) return OutgoingMessageStatus.FULLY_SENT;
Predicate<ByteBuffer> p = b -> b.position() > 0;
boolean sent = p.test(buffers.get(0));
if(!sent) p = p.negate();
return buffers.stream().skip(1).allMatch(p)? sent?
    OutgoingMessageStatus.FULLY_SENT:
    OutgoingMessageStatus.WAS_NOT_SENT:
    OutgoingMessageStatus.PARTIALLY_SENT;
}

第一个元素的状态决定了我们必须检查的条件。只要有一个矛盾的元素,allMatch 立即返回,我们有一个PARTIALLY_SENT 的情况。否则,所有元素都像第一个一样匹配,这意味着“全部发送”或“未发送”。

空列表的预检查产生与原始代码相同的行为,并确保get(0) 永不中断。


如果您确实有一个 Stream 而不是可以多次迭代的源,则没有简单的捷径解决方案,因为这需要有状态的谓词。但是,有一些简单的解决方案可以处理所有元素。

Map<Boolean,Long> result=getBuffers().stream()
    .collect(Collectors.partitioningBy(b -> b.position() > 0, Collectors.counting()));
return
    result.getOrDefault(false, 0L)==0?
        OutgoingMessageStatus.FULLY_SENT:
    result.getOrDefault(true, 0L)==0?
        OutgoingMessageStatus.WAS_NOT_SENT:
        OutgoingMessageStatus.PARTIALLY_SENT;

return super.getBuffers().stream()
    .map(b -> b.position() > 0?
              OutgoingMessageStatus.FULLY_SENT: OutgoingMessageStatus.WAS_NOT_SENT)
    .reduce((a,b) -> a==b? a: OutgoingMessageStatus.PARTIALLY_SENT)
    .orElse(OutgoingMessageStatus.FULLY_SENT);

【讨论】:

  • @Holger 最后一段代码绝对有病! +1
  • @Stuart Marks:我知道。但由于没有明确指定,我决定在这里使用getOrDefault,这并没有什么坏处。顺便说一句,the special map returned by partitioningBy应该覆盖一些典型的Map 操作。目前,它只实现了entrySet(),这使得即使是一个简单的get(带有Boolean)也是一个非常昂贵的操作,特别是考虑到每次都会实例化一个new条目集,即使是一个简单的size() 电话。
  • @Holger Spec 更改由 JDK-8170943 涵盖,集成在 JDK 9 build 150(2016-12-22 发布)中。 JDK-8170945 涵盖了优化。不确定什么时候会修复,但会在某个时候修复。
  • @Stuart Marks:关于 JDK-8170945,containsKeyObject 参数类型,所以它必须是 public boolean containsKey(Object key) { return key instanceof Boolean; }。此外,我还投票给@Override public void forEach(BiConsumer&lt;? super Boolean, ? super V&gt; c) { c.accept(Boolean.FALSE, forFalse); c.accept(Boolean.TRUE, forTrue); } 以释放内部迭代的好处,如果我们心情好,也可以拥有@Override public boolean containsValue(Object value) { return Objects.equals(forTrue, value) || Objects.equals(forFalse, value); }...
  • @Holger D'oh,这些方法当然不是通用的!另外,感谢您的其他建议。
【解决方案2】:

你可以使用filter(),然后统计通过它的元素个数:

Stream<ByteBuffer> buffers = super.getBuffers().stream();
int matches = buffers.filter(b -> b.position() > 0).count();
if (matches == super.getBuffers().size()) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (matches == 0) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

这里假设Stream (super.getBuffers()) 的数据源有一个size() 方法。如果没有,您可以使用附加变量计算ByteBuffers 的总数(我知道不太优雅):

int[] total = {0};
int matches = buffers.filter(b -> {total[0]++; return b.position() > 0;}).count();
if (matches == total[0]) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (matches == 0) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

这种方法的缺点是当只有一些元素通过过滤器时它不会很快失败(即输出应该是OutgoingMessageStatus.PARTIALLY_SENT)。也许您可以使用一些reduce 操作来返回三个可能的输出之一,并且只处理必要的元素。

【讨论】:

  • @Holger 这是真的。 allMatch() 仅在返回 true 时才需要完全迭代。
  • 请注意,iterating twice 是最有效的方式。由于第一个元素只能是匹配或不匹配,allMatchnonMatch 将在检查第一个元素后立即返回。如果需要,另一项检查将是短路。所以它最多有 n + 1 次检查,但可能更少。我还提供了一个短路解决方案,最多有 n 个检查。由于您的解决方案需要存在 size() 方法,因此它还依赖于 super.getBuffers() 返回一个 Collection。
  • @Holger super.getBuffers() 是一个列表(由 OP 评论)。
  • 我知道。所以迭代两次是可能的。
  • 我添加了一个基于reduce 的解决方案,我喜欢它的清晰性,但Stream API 中没有捷径reduce(我知道,这已经被其他人请求过)...我还添加了一个partitioningBy 解决方案,这是您的第二个变体的干净版本。
【解决方案3】:

你需要一个集合来重用它。

您可以使用count() 代替collect(toList()),具体取决于您是否需要结果。

List<ByteBuffer> list = super.getBuffers();
List<ByteBuffer> buffers = list.stream().filter(b -> b.position() > 0).collect(toList());
if (buffers.size() == list.size()) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.isEmpty()) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

【讨论】:

  • 所以你收集到List,只是为了得到count()
【解决方案4】:

我只是想用不同的类型和谓词解决你刚才遇到的同一个问题。没有找到任何令人满意的答案,所以我以这种方式实现了自己:

Enum 不仅包含truefalse 来满足谓词,还包含NONE(用于空集合)和ENTANGLED(表示集合的某些元素满足谓词和有些没有):

public enum QuantumBoolean {
    NONE, TRUE, FALSE, ENTANGLED;

    public static QuantumBoolean fromBoolean(boolean bool) {
        if (bool) return QuantumBoolean.TRUE;
        return QuantumBoolean.FALSE;
    }
}

为了利用 Java Stream API 并在我需要使用特定参数调用 .reduce() 时迭代集合,因此我将它们打包到新类中:

public class QuantumBooleanReducer<T> {

    private final Predicate<T> predicate;
    public static final QuantumBoolean IDENTITY = QuantumBoolean.NONE;

    public QuantumBooleanReducer(Predicate<T> predicate) {
        this.predicate = predicate;
    }

    public final QuantumBoolean accumulator(QuantumBoolean state, T element) {
        QuantumBoolean newState = QuantumBoolean.fromBoolean(predicate.test(element));
        if (QuantumBoolean.NONE.equals(state))
            return newState;
        if (newState.equals(state))
            return state;
        return QuantumBoolean.ENTANGLED;
    }

    public static QuantumBoolean combiner(QuantumBoolean state1, QuantumBoolean state2) {
        if (state1.equals(state2))
            return state1;
        if (QuantumBoolean.NONE.equals(state1))
            return state2;
        if (QuantumBoolean.NONE.equals(state2))
            return state1;
        return QuantumBoolean.ENTANGLED;
    }
}

然后你可以通过调用类似的方法来解决你的问题:

switch(buffers.stream()
              .reduce(QuantumBooleanReducer.IDENTITY,
                      new QuantumBooleanReducer<ByteBuffer>(buffer -> buffer.position() > 0)::accumulator,
                      QuantumBooleanReducer::combiner)) {
            case FALSE:
                return OutgoingMessageStatus.WAS_NOT_SENT;
            case TRUE:
                return OutgoingMessageStatus.FULLY_SENT;
            case ENTANGLED:
                return OutgoingMessageStatus.PARTIALLY_SENT;
            default:
                throw new IllegalStateException("Unexpected Quantum Boolean");
}

【讨论】:

  • 这不会短路
  • 没错,但真的可以判断集合是否完全、部分或根本不满足谓词短路?因此,您需要对集合进行完整的一个迭代周期。还是我什么都没看到?
猜你喜欢
  • 2022-12-09
  • 1970-01-01
  • 2017-09-19
  • 2016-05-07
  • 1970-01-01
  • 2020-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多