Java 8 переписывает комплекс для цикла с использованием потоков - PullRequest
0 голосов
/ 25 декабря 2018

Можно ли переписать сложный цикл for, как это, просто используя потоки Java 8?Все, что я придумаю, кажется более раздутым, чем просто оставить код, как показано ниже, с нормальным циклом 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?(И я не хочу бросать исключение!) </p>

Спасибо

Ответы [ 6 ]

0 голосов
/ 25 декабря 2018

Вот аналогичное решение с использованием Java 8.

Первая карта '(', ')' и другие символы для 1, -1 и 0 соответственно.Затем вычислите совокупную сумму и проверьте, что каждая частичная сумма 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;
0 голосов
/ 25 декабря 2018

Вы можете получить досрочное завершение оценки потока с помощью одной из операций терминала, которая его поддерживает.Их оказывается сравнительно мало, но если вы готовы терпеть незначительные злоупотребления и используете 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;
}

Это очень похоже на ваш оригинальный цикл.

0 голосов
/ 25 декабря 2018

Следующий код будет делать то, что вы хотите, и меньше, чем ваш исходный код, но он запутанный и всегда обрабатывает все символы, то есть не останавливается рано, если обнаружен несбалансированный ).

Однаков отличие от некоторых других ответов здесь, он не нарушает правила потока, поддерживая состояние вне потока.

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: найдено несбалансированным (

Длинная версия thтот же код, использующий операторы 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);
}
0 голосов
/ 25 декабря 2018

Вы не должны.

Лямбда и Stream не являются заменой для всех сложных for петель.Хотя вы можете использовать Stream, как вы это сделали, это не значит, что это лучше для глаз (что легче понять?) И для производительности (вы наверняка что-то потеряли из-за AtomicInteger против intоснованный на операции, но вы могли бы вместо этого использовать массив int[]).

  • Вы не можете выйти из цикла как можно скорее, если только вы не используете исключение, но вы можете немного сузить свой тест (и ты должен это на скамейке).Возможно, вы могли бы подумать об использовании filter после операции map, но это не облегчит чтение.
  • Возможно, вам следует придерживаться pure function , например: вам, вероятно, следуетне имеет побочного эффекта (на AtomicInteger).
0 голосов
/ 25 декабря 2018

Потоки do имеют концепцию досрочного завершения, но только если операция терминала действительно поддерживает это.

Из описанной вами операции forEachOrdered будет повторяться по каждый элемент в потоке, и он не имеет возможности рано сломаться.Помните: потоков может быть бесконечным , поэтому прерывание потока на раннем этапе при упорядочении каждого из них может рассматриваться как ошибка времени выполнения.

По сути, я бы на самом делеРекомендуем придерживаться варианта цикла над вариантом потока, поскольку вариант цикла дает возможность завершить работу раньше.То, что вы написали для варианта потока, на самом деле разумно, учитывая, с какими ограничениями он должен иметь дело.

0 голосов
/ 25 декабря 2018

Во-первых, я хотел бы упомянуть, что код, который включает побочные эффекты, обычно плохо работает с потоками, и по этой причине я бы предложил продолжить с императивным подходом:

  1. короткое замыкание кода
  2. он читается как написано

Что касается:

Работает нормально, но перебирает всю строку, хотя иногда это может быть потрачено впустуюНапример, в случае строки

Любая попытка закорочить решение потока, которое вы показали, будет включать побочные эффекты , и это, вВообще, не рекомендуется.

Побочные эффекты в поведенческих параметрах для потоковых операций, как правило, не приветствуются, поскольку они часто могут привести к невольным нарушениям требования о безгражданстве, а также другим угрозам безопасности потока.Если поведенческие параметры имеют побочные эффекты, если не указано иное, нет никаких гарантий относительно видимости этих побочных эффектов для других потоков, а также нет никаких гарантий того, что различные операции над «одним и тем же» элементом в одном и том же потоковом конвейеревыполняются в одном и том же потоке.

Вывод состоит в том, что потоки не всегда являются решением всех проблем, а скорее для конкретных случаев, и этот случай определенно не является дружественным для потока.

...