简体   繁体   English

Java 8使用流重写for循环复合体

[英]Java 8 rewriting a complex for loop using streams

Is it possible to rewrite a complicated for loop like this just using java 8 streams? 是否可以使用java 8流重写这样的复杂for循环? Anything I come up with seems more bloated, then just leaving the code as is below with a normal for loop. 我想出的任何东西看起来都比较臃肿,然后只需将代码保留为下面的正常for循环。

public static  boolean isBalanced(String text) {
    int count = 0;
    for(int i = 0; i < text.length(); i++ ) {
        if (text.charAt(i) == ')') {
            count--;
        } else if (text.charAt(i) == '(') {
            count++;
        }
        if (count < 0) {
            return false;
        }
    }
    return count == 0;
}


Using Streams 使用Streams

public static boolean isBalanced2(String text) {
    AtomicInteger count = new AtomicInteger(0);

    text.chars()
        .forEachOrdered(x -> {
             if (x == ')') {
                 count.getAndDecrement();
             } else if (x == '(') {
                 count.getAndIncrement();
             }
        });

    return count.get() == 0;
}

It works ok but it iterates through the whole string, when sometimes that might be wasted computation for example in the case of the string ")......" 它工作正常,但它遍历整个字符串,有时可能会浪费计算,例如在字符串“)的情况下......”

It doesn't seem possible to exit the stream as soon as count is < 0 ? 一旦count <0,似乎不可能退出流? (And I dont want to throw an exception!) (而且我不想抛出异常!)

Thanks 谢谢

Firstly, Id like to mention code which involves side-effects typically does not work well with streams for that reason alone I'd suggest proceeding with the imperative approach as: 首先,我想提一下涉及副作用的代码通常不能很好地与流单独使用,因此我建议继续使用命令式方法:

  1. the code short circuits 代码短路
  2. it's readable as written 它是可写的

As for: 至于:

It works ok but it iterates through the whole string, when sometimes that might be wasted computation for example in the case of the string 它工作正常,但它遍历整个字符串,有时可能会浪费计算,例如在字符串的情况下

Any attempt to short-circuit the stream solution you've shown will involve side-effects and this, in general, is discouraged. 任何使您显示的流解决方案短路的尝试都会涉及副作用,而且一般来说,这是不鼓励的。

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards. 通常,不鼓励行为参数对流操作的副作用,因为它们通常会导致无意中违反无国籍要求以及其他线程安全危险。 If the behavioral parameters do have side-effects, unless explicitly stated, there are no guarantees as to the visibility of those side-effects to other threads, nor are there any guarantees that different operations on the "same" element within the same stream pipeline are executed in the same thread. 如果行为参数确实有副作用,除非明确说明,否则不能保证这些副作用对其他线程的可见性,也不保证对同一流管道中“相同”元素的不同操作在同一个线程中执行。

The conclusion is that streams are not always the solution to all problems rather for specific cases and this case is definitely not stream friendly. 结论是,流并不总是所有问题的解决方案,而是特定情况,这种情况绝对不是流友好的。

You should not. 你不应该。

Lambda and Stream are not replacement for all complicated for loop. Lambda和Stream不是所有复杂for循环的替代品。 While you may use a Stream as you've done, this does not means it is better for the eye (what is easier to understand?) and for performance (you surely lost something due to the AtomicInteger vs int based operation but you could probably use a int[] array instead). 虽然你可以像你一样使用Stream ,但这并不意味着它对眼睛更好(更容易理解?)和性能(由于AtomicInteger和基于int的操作,你肯定会丢失一些东西,但你可能改为使用int[]数组)。

  • You can't exit the loop as soon as possible, unless you use exception, but you can narrow your test a little (and you should bench it). 除非你使用异常,否则你不能尽快退出循环,但是你可以稍微缩小测试范围(你应该对它进行测试)。 You could probably think of using filter after map operation but that won't make it easier to read. 您可能会想到在map操作之后使用filter ,但这不会使其更容易阅读。
  • You should probably stick to pure function , eg: you should probably not have a side effect (on the AtomicInteger ). 你可能应该坚持使用纯函数 ,例如:你应该没有副作用(在AtomicInteger )。

The following code will do what you want, and is smaller than your original code, but it's convoluted and always processes all characters, ie doesn't stop early if unbalanced ) is detected. 下面的代码会做你想要什么,比你原来的代码更小,但它是令人费解的,总是处理所有的字符,如果不平衡,即不提前停止)检测。

However, unlike some other answers here, it doesn't violate the stream rules by maintaining state outside the stream. 但是,与此处的其他答案不同,它不会通过维护流外的状态来违反流规则。

private static boolean isBalanced(String text) {
    return 0 == text.chars()
            .reduce(0, (n, c) -> n < 0 ? n : c == '(' ? n + 1 : c == ')' ? n - 1 : n);
}

The logic is as follows: 逻辑如下:

  • Keep a running total representing the nesting level, ie increment the value when ( is found and decrement the value when ) is found. 保持一个表示嵌套级别的运行总计,即在找到(找到并递减值)时递增值。

  • If the total goes below 0, stop updating it, ie when an unbalanced ) is found, keep final total at -1. 如果总低于0,停止更新它,即当不平衡)被发现,最终保持在总-1。

The result of the reduce operation is then: 然后, reduce操作的结果是:

  • 0 : All ( have balanced ) 0 :全部(均衡)

  • -1 : Found unbalanced ) -1 :发现不平衡)

  • >0 : Found unbalanced ( >0 :发现不平衡(

Long version of the same code, using if statements instead of conditional ternary operator. 长版本的相同代码,使用if语句而不是条件三元运算符。

private static boolean isBalanced(String text) {
    int finalLevel = text.chars().reduce(0, (lvl, ch) -> {
        if (lvl < 0)
            return lvl; // Keep result of -1 for unbalanced ')'
        if (ch == '(')
            return lvl + 1;
        if (ch == ')')
            return lvl - 1;
        return lvl;
    });
    return (finalLevel == 0);
}

Here is a similar solution to yours using Java 8. 以下是使用Java 8的类似解决方案。

First map '(' , ')' and other characters to 1 , -1 and 0 respectively. 首先将'('')'和其他字符分别映射到1-10 Then compute a cumulative sum and check that each partial sum ps >= 0 and the final sum s == 0 . 然后计算累积和并检查每个部分和ps >= 0并且最终总和s == 0 By using allMatch for the partial sum checking the process is short-circuiting. 通过使用allMatch进行部分和检查,该过程是短路的。

public static boolean isBalanced(String text) {
    AtomicInteger s = new AtomicInteger();
    return text.chars()
            .map(ch -> (ch == '(') ? 1 : (ch == ')') ? -1 : 0)
            .map(s::addAndGet)
            .allMatch(ps -> ps >= 0) && s.get() == 0;
}

Here is a solution that supports multiple different parentheses (requires some IntStack implementation): 这是一个支持多个不同括号的解决方案(需要一些IntStack实现):

IntStack stack = ...;
return text.chars()
        .map("(){}[]"::indexOf)
        .filter(i -> i >= 0)
        .allMatch(i -> {
            int form = i / 2; // 0 = (), 1 = {}, 2 = []
            int side = i % 2; // 0 = left, 1 = right
            if (side == 0) {
                stack.push(form);
                return true;
            } else {
                return stack.size() != 0 && stack.pop() == form;
            }
        }) && stack.size() == 0;

Streams do have the concept of early termination, but only if the terminal operation actually supports it. Streams 确实具有提前终止的概念,但前提是终端操作实际上支持它。

From the operation you describe, forEachOrdered is going to iterate over each element in the stream and it does not have the ability to break early. 根据您描述的操作, forEachOrdered将迭代流中的每个元素,并且它无法提前中断。 Remember: streams could be infinite , so breaking the stream early while ordering over each one could be seen as a runtime error. 请记住: 可能是无限的 ,因此在对每个流进行排序时提前断开流可能会被视为运行时错误。

In essence, I'd actually encourage you to stick with the loop variant over the stream variant, since the loop variant gives you the ability to terminate early. 实质上,我实际上鼓励你坚持使用循环变体而不是流变体,因为循环变体使你能够提前终止。 What you have written for the stream variant is actually reasonable, given what constraints it has to deal with. 您为流变体编写的内容实际上是合理的,考虑到它必须处理的约束。

You can get early termination of a stream evaluation from one of the terminal operations that supports it. 您可以从支持它的终端操作之一提前终止流评估。 These turn out to be comparatively few, but if you're willing to tolerate some minor abuse, and you're using Java 9 or later, then you can use takeWhile() to perform early termination pretty universally. 结果相对较少,但是如果你愿意忍受一些轻微的滥用,而你正在使用Java 9或更高版本,那么你可以使用takeWhile()来普遍地提前终止。 The trick (and also the abuse) is to use a state-retaining predicate. 诀窍(以及滥用)是使用状态保留谓词。 For example: 例如:

public static boolean isBalanced(String text) {
    final int[] state = new int[0];

    text.chars().takeWhile(c -> {
        if (c == '(') state[0]++; if (c == ')') state[0]--; return state[0] >= 0; 
    });

    return state[0] == 0;
}

This is very closely analogous to your original loop. 这与您的原始循环非常相似。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM