Как пребразовать IntStream в for. Как применяется IntUnaryOperator

Рейтинг: 0Ответов: 2Опубликовано: 07.06.2023

Код из codesignal.com Помогите разобраться, как он работает. В частности, можно ли IntStream переделать как for для понимания.

IntUnaryOperator - почему выбран и что он делает. Представление о функциональных интерфейсах имею, но на уровне примитивных примеров.

boolean solution(char[][] grid) {
    return IntStream.range(0, 9)
        .allMatch(n ->
                  isValid(grid, k -> k, k -> n) &&
                  isValid(grid, k -> n, k -> k) &&
                  isValid(grid, k -> (n / 3) * 3 + k / 3, k -> (n % 3) * 3 + k % 3));
}

boolean isValid(char[][] grid, IntUnaryOperator rowSelector, IntUnaryOperator colSelector) {
    Set<Character> s = new HashSet<>();
    for (int k = 0; k < 9; k++) {
        int r = rowSelector.applyAsInt(k);
        int c = colSelector.applyAsInt(k);
        if (grid[r][c] == '.') {
            continue;
        }
        if (s.contains(grid[r][c])) {
            return false;
        }
        s.add(grid[r][c]);
    }
    return true;
}

Ответы

▲ 1Принят

Сам по себе IntStream.range(0, 9) - это примерно как for (int i = 0; i < 9; i++).

allMatch - это проверка, что все элементы из stream соответствуют указанному условию (чтобы allMatch вернул true, нужно чтобы для каждого элемента из stream предикат вернул true). Если переделывать в обычный цикл, то внутри цикла должен быть if, который выйдет из цикла, если предикат вернул false.

Замена на for и if будет примерно такая:

boolean solution(char[][] grid) {
    for (int i = 0; i < 9; i++) {
        IntPredicate predicate = n ->
                isValid(grid, k -> k, k -> n) &&
                        isValid(grid, k -> n, k -> k) &&
                        isValid(grid, k -> (n / 3) * 3 + k / 3, k -> (n % 3) * 3 + k % 3);

        if (!predicate.test(i)) {
            return false;
        }
    }

    return true;
}

IntUnaryOperator - просто функциональный интерфейс, который можно реализовать функцией, принимающей целое число и возвращающим целое число (т.е. операция над целым числом).

Примерно то же самое, что Function<Integer, Integer>. В коде метода isValid можно заменить IntUnaryOperator на Function<Integer, Integer> и applyAsInt на просто apply, и все будет работать как работало.

▲ 0

Заменить IntStream на цикл for в методе solution достаточно просто, но читабельность кода вряд ли улучшится от этого:

boolean solution(char[][] grid) {
    for (int n = 0; n < 9; n++) {
        if (!(isValid(grid, k -> k, k -> n) &&
            isValid(grid, k -> n, k -> k) &&
            isValid(grid, k -> (n / 3) * 3 + k / 3, k -> (n % 3) * 3 + k % 3)
        )) {
            return false;
        }
    }
    return true;
}

IntUnaryOperator в данном случае представляют собой некоторые простые функции для выбора ряда или строки для заданного числа.

  1. isValid(grid, k -> k, k -> n)
    Здесь rowSelector возвращает свой аргумент, а colSelector -- константное значение в диапазоне [0 .. 9).
    Таким образом внутри метода isValid при подставлении числа k в том же диапазоне [0 .. 9) будет выполняться итерация по строкам: r ∈ [0..9), c = n для заданного n-го столбца, т.е. выполняется вертикальная проверка.
  2. isValid(grid, k -> n, k -> k)
    Аналогично, rowSelector возвращает константное значение в диапазоне [0 .. 9), а colSelector -- свой аргумент, то есть внутри метода isValid будет выполняться итерация по столбцам для заданной n-ой строки: r = n, с ∈ [0..9) -- выполняется горизонтальная проверка
  3. isValid(grid, k -> (n / 3) * 3 + k / 3, k -> (n % 3) * 3 + k % 3) - для проверки квадратов 3х3 внутри входного массива символов 9х9.

То есть, изменяя вычисление индексов, достаточно использовать одну и ту же функцию для проверки строки / столбца / квадрата.


Решение в старом "нефункциональном" стиле могло бы выглядеть так (с использованием вложенного цикла):

static boolean solution(char[][] grid) {
    for (int n = 0; n < 9; n++) {
        Set<Character> row = new HashSet<>();
        Set<Character> col = new HashSet<>();
        Set<Character> sqr = new HashSet<>();
        for (int k = 0; k < 9; k++) {
            // рассматриваем строку
            char r = grid[n][k];
            if (r != '.' && !row.add(r)) return false;
            
            // рассматриваем столбец
            char c = grid[k][n];
            if (c != '.' && !col.add(c)) return false;
            
            // рассматриваем квадрат 3х3
            char s = grid[(n / 3) * 3 + k / 3][ (n % 3) * 3 + k % 3];
            if (s != '.' && !sqr.add(s)) return false;
        }
    }
    return true;
}

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