【问题标题】:Java 8 rewriting a complex for loop using streamsJava 8 使用流重写复杂的 for 循环
【发布时间】:2019-05-23 21:44:05
【问题描述】:

是否可以仅使用 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;
}

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

似乎不可能在 count

谢谢

【问题讨论】:

  • 我会使用for (char c : test.toCharArray()) {(或等效的代码点)。
  • 你可以给人们时间来回应......
  • public static boolean isBalanced2(String text) { AtomicInteger count = new AtomicInteger(0);文本.chars()。 forEachOrdered(x -> { if (x == ')') { count.getAndDecrement(); } else if (x == '(') { count.getAndIncrement(); } });返回计数.get() == 0;它工作得很好,只是它遍历整个字符串,有时它可能会浪费计算,例如在字符串“)......”的情况下
  • 我建议也解释一下用例,我的意思是你到底需要什么?你为什么要返回if (count &lt; 0) 和return count == 0; 更好地解释你想要什么@Pat
  • 这篇文章中的Stream 方法对于String 像)( 这样的String 是否失败?早期的回报不仅仅是性能调优,本质上是算法。大括号的顺序很重要。

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


【解决方案1】:

这是与您使用 Java 8 的类似解决方案。

首先将'('、')' 和其他字符分别映射到1、-1 和0。然后计算一个累积和并检查每个部分和ps &gt;= 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;

【讨论】:

  • 这是我能看到的最佳答案。 OP,请接受这个答案。
  • @KunalPuri:这是将流硬塞进一个不容易维护的目的,不允许它们利用流的主要优势之一,并且代码会更难长期保持。强烈反对您认为这是“最佳”答案。
  • @Makoto 通过最佳答案,我想说的是,这是使用流的最佳答案。我完全同意你的看法。
  • @Makoto 还有一件事。我觉得即使是基于计数的方法也不是那么容易维护。当不同的括号出现时,如花括号、方括号等,解决方案将很困难。相反,我觉得这应该使用堆栈和映射来完成。
  • @KunalPuri 添加了另一个使用堆栈来支持不同括号的解决方案。
【解决方案2】:

首先,我想提一下涉及副作用的代码通常不适用于流,仅出于这个原因,我建议继续使用命令式方法:

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

至于:

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

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

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

结论是流并不总是解决所有问题,而是针对特定情况,这种情况绝对不是流友好的。

【讨论】:

  • 我同意这不是一个流友好的任务,但它can be done 没有使用副作用,但没有短路。
  • @Andreas 当然,但我说“任何短路您展示的流解决方案的尝试都会涉及副作用,通常不鼓励这样做。 "
  • 是的,但是短路只发生在不平衡),而不是不平衡(,所以短路更多的是一个很好的提升,而不是一个要求。因此,只要结果正确,不短路的流解决方案可能是可以接受的。
  • @Andreas 对,我只是想回答 “它工作正常,但它会遍历整个字符串,有时这可能会浪费计算,例如在字符串的情况下。 ...” 帖子的一部分听起来他们仍然想保持短路,但是我同意你的观点 “不短路的流解决方案可能 可以接受,只要结果正确。”
【解决方案3】:

您可以从支持它的终端操作之一提前终止流评估。这些结果相对较少,但如果您愿意容忍一些轻微的滥用,并且您使用的是 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;
}

这与您的原始循环非常相似。

【讨论】:

    【解决方案4】:

    下面的代码会做你想做的事,并且比你的原始代码小,但它是复杂的并且总是处理所有字符,即如果检测到不平衡的),它不会提前停止。

    但是,与此处的其他一些答案不同,它不会通过在流之外维护状态来违反流规则。

    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:发现不平衡)

    • &gt;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);
    }
    

    【讨论】:

    • 请注意,这里描述的归约函数不是关联的。此答案中的场景将起作用,但它依赖于从左到右的顺序执行。如果归约函数被修改或改变用途,特别是如果它是并行运行的,它不会给出正确的结果。
    【解决方案5】:

    你不应该。

    Lambda 和 Stream 不能替代所有复杂的 for 循环。虽然您可以使用Stream,但这并不意味着它对眼睛更好(什么更容易理解?)和性能(由于AtomicInteger 与int,您肯定会丢失一些东西基于操作,但您可能可以使用 int[] 数组代替)。

    • 你不能尽快退出循环,除非你使用异常,但是你可以稍微缩小你的测试范围(你应该把它放平)。您可能会考虑在map 操作之后使用filter,但这不会使其更易于阅读。
    • 您可能应该坚持使用pure function,例如:您可能不应该有副作用(在AtomicInteger 上)。

    【讨论】:

      【解决方案6】:

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

      根据您描述的操作,forEachOrdered 将遍历流中的 每个 元素,并且它没有能力提前中断。请记住:流可能是无限的,因此在对每个流进行排序时提前中断流可能被视为运行时错误。

      本质上,我实际上鼓励您坚持使用循环变体而不是流变体,因为循环变体使您能够提前终止。考虑到它必须处理的约束条件,您为流变体编写的内容实际上是合理的。

      【讨论】:

      • 感谢您的回复。你回答了我的问题 - 所以有时我们最好只使用普通循环而不是强迫自己使用流。
      猜你喜欢
      • 1970-01-01
      • 2022-01-19
      • 2018-11-19
      • 2018-09-17
      • 2020-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多