Проблема FizzBuzz - это классическая задача, которая часто встречается на собеседованиях для программистов. Обычно она формулируется так:

Создайте программу, которая выводит числа от 1 до n.

- Если число делится на 3, выведите 'Fizz';
- если число делится на 5, выведите 'Buzz';
- если число делится и на 3 и на 5, выведите 'FizzBuzz'.

Это упражнение подтолкнуло меня к идее поработать над задачами с использованием Stream API, используя заранее определенные предикаты fizz и buzz. Наша цель – создать различные фильтры для потока чисел, используя эти предикаты.

Мы разберем решения четырех простых задач, а более сложная и интересная останется вам.

Сначала определим предикаты fizz и buzz:

IntPredicate fizz = i -> i % 3 == 0;
IntPredicate buzz = i -> i % 5 == 0;

Поток целых чисел от 1 до 20 создадим с помощью метода IntStream::rangeClosed:

var numbers = IntStream.rangeClosed(1, 20);

Цель всех следующих задач – отфильтровать поток чисел, создав предикат fizzBuzz на основе уже определённых предикатов.

Задача №1. Фильтрация чисел, кратных 3 и 5

Необходимо отфильтровать числа, которые делятся нацело на 3 и 5.

В этом случае есть два способа решить задачу. Например, мы можем использовать логический оператор &&:

IntPredicate fizzBuzz = i -> fizz.test(i) && buzz.test(i);

Но более предпочтительный и удобный вариант – использовать метод and, принадлежащий интерфейсу IntPredicate:

IntPredicate fizzBuzz = fizz.and(buzz);

assertThat(numbers.filter(fizzBuzz))
        .as("Numbers divisible by three and five")
        .containsExactly(15);

Задача №2. Фильтрация чисел, кратных 3 или 5

Здесь решение аналогично предыдущей задаче, но мы используем метод or вместо and:

IntPredicate fizzBuzz = fizz.or(buzz);

assertThat(numbers.filter(fizzBuzz))
        .as("Numbers divisible by three or five")
        .containsExactly(3, 5, 6, 9, 10, 12, 15, 18, 20);

Задача №3. Фильтрация чисел, не кратных 3 и 5

Для того чтобы отфильтровать числа, которые не делятся ни на 3, ни на 5, можно использовать метод IntStream::negate. Решение выглядит следующим образом:

IntPredicate fizzBuzz = fizz.or(buzz).negate();

assertThat(numbers.filter(fizzBuzz))
        .as("Numbers not divisible by three or five")
        .containsExactly(1, 2, 4, 7, 8, 11, 13, 14, 16, 17, 19);

Задача №4. Фильтрация чисел, кратных 3 или 5, но не обоим

Следующая задача – отфильтровать числа, которые делятся на 3 или на 5, но не делятся нацело на оба числа одновременно. Здесь можно использовать все методы интерфейса IntStream, однако лучший вариант – исключающее ИЛИ:

IntPredicate fizzBuzz = i -> fizz.test(i) ^ buzz.test(i);

assertThat(numbers.filter(fizzBuzz))
        .as("Numbers divisible by either three or five")
        .containsExactly(3, 5, 6, 9, 10, 12, 18, 20);

Бонусная задача

В заключение предлагаю вам проверить ваш навык составления предикатов. Составьте предикат для чисел внутри последовательностей FizzBuzz исключая границы. Последовательность начинается числом, удовлетворяющим предикату fizz, и заканчивается числом, удовлетворяющим предикату buzz.

Например, для последовательности чисел от 1 до 20, результат должен выглядеть следующим образом:

1, 2, (3, 4, 5), (6, 7, 8, 9, 10), 11, (12, 13, 14, 15), 16, 17, (18, 19, 20)

Задача выбрать числа внутри скобок не включая границы.

Для удобства можете использовать следующий код для теста:

@Test
@DisplayName("Filter out numbers between integers divisible by three and by five.")
void numbers_between_integers_divisible_by_three_and_by_five() {
    var numbers = IntStream.rangeClosed(1, 20);

    // TODO: Define the predicate
    IntPredicate fizzBuzz = i -> false;

    assertThat(numbers.filter(fizzBuzz))
            .as("Numbers between interegers divisible by three and by five")
            .containsExactly(4, 7, 8, 9, 13, 14, 19);
}

Надеюсь, что последняя задачка более интересная чем предыдущие. Напишите в комментариях, насколько быстро вам удалось её решить.

Комментарии (1)


  1. Rabestro Автор
    11.09.2023 13:39