Java 8使用流重写复杂的for循环

时间:2018-12-24 23:25:03

标签: java for-loop java-8 java-stream

是否仅使用Java 8流就可以重写一个复杂的for循环?我想出的任何东西似乎都变得过时了,然后将下面的代码留给普通的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;
}


使用流

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;
}

它可以正常工作,但是会遍历整个字符串,有时有时会浪费计算量,例如在字符串“)......”

计数<0似乎不可能立即退出流? (而且我不想抛出异常!)

谢谢

6 个答案:

答案 0 :(得分:2)

首先,我想提一下涉及副作用的代码通常仅由于这个原因不能与流一起很好地工作,我建议采用命令式方法,如下所示:

  1. 代码短路
  2. 可以书面阅读

至:

  

它可以正常工作,但是有时会遍历整个字符串   例如在字符串的情况下,这可能会浪费计算

任何使您显示的流解决方案短路的尝试都将涉及side-effects ,通常不建议这样做。

  

行为参数对流操作的副作用是   一般的,灰心丧气的,因为他们经常会导致无意识的违规   无状态要求以及其他线程安全性   危害。如果行为参数确实有副作用,除非   明确指出,不能保证   对其他线程的那些副作用,也没有任何保证   同一流中对“相同”元素的不同操作   管道在同一线程中执行。

结论是,对于某些情况,流并不总是解决所有问题的方法,并且这种情况绝对不是流友好的。

答案 1 :(得分:2)

你不应该。

Lambda和Stream不能代替所有复杂的for循环。尽管您可能会用Stream,但这并不意味着它对眼睛(更容易理解?)和性能(由于{{1}与基于AtomicInteger的操作相比,但您可以改用int数组)。

  • 除非使用异常,否则您无法尽快退出循环,但是可以稍微缩小测试范围(应该进行基准测试)。您可能会想到在int[]操作之后使用filter,但这不会使阅读变得更容易。
  • 您可能应该坚持使用pure function,例如:您可能应该不会对map产生副作用。

答案 2 :(得分:2)

以下代码可以满足您的要求,并且比原始代码要小,但是它很复杂,并且始终处理所有字符,即,如果检测到不平衡的)不会早停。

但是,与这里的其他一些答案不同,它不会通过保持流外部的状态来违反流规则。

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

逻辑如下:

  • 保留表示嵌套级别的运行总计,即,找到(时增加值,找到)时减少值。

  • 如果总数低于0,请停止更新,即找到不平衡的)时,将最终总数保持为-1。

然后reduce操作的结果是:

  • 0 :所有(都具有平衡的)

  • -1 :发现不平衡的)

  • >0 :发现不平衡的(

使用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);
}

答案 3 :(得分:2)

以下是使用Java 8的类似解决方案。

第一个映射'('')'和其他字符分别映射到1-10。然后计算一个累积和,并检查每个部分和ps >= 0和最终和s == 0。通过使用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;
}

这是一个支持多个不同括号的解决方案(需要一些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;

答案 4 :(得分:0)

do 具有提前终止的概念,但前提是终端操作确实支持它。

根据您描述的操作,forEachOrdered将在流中的每个元素上进行迭代,并且不具备提前中断的能力。请记住:可以是无限的,因此在对每个流进行排序时尽早中断流可被视为运行时错误。

从本质上讲,我实际上鼓励您在流变体上坚持循环变体,因为循环变体使您能够尽早终止。考虑到必须处理的约束,您为stream变量编写的内容实际上是合理的。

答案 5 :(得分:0)

您可以从支持它的终端操作之一中尽早终止流评估。事实证明,这些比较少,但是如果您愿意忍受一些轻微的滥用,并且使用的是Java 9或更高版本,则可以使用takeWhile()来普遍执行早期终止。技巧(以及滥用)是使用保留状态的谓词。例如:

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;
}

这非常类似于您的原始循环。