Проблема 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);
}
Надеюсь, что последняя задачка более интересная чем предыдущие. Напишите в комментариях, насколько быстро вам удалось её решить.
Rabestro Автор
Source code: https://github.com/rabestro/fizzbuzz-filter