Реактивное программирование – парадигма описания вычислений как преобразований потоков событий во времени. В центре внимания находятся данные как поток и зависимости как явный граф, что позволяет строго формализовать реакцию системы на изменения входов. В данной работе изложен понятийный аппарат (событие, поток, наблюдатель, оператор, время), формальная модель потоков, ключевые правила проектирования реактивных решений, типовые алгоритмические приёмы (окна, буферы, фильтрация, агрегация, слияние, упорядочивание) и аспекты корректности (детерминизм, управление скоростью – backpressure, обработка ошибок). Показано, как навыки, требуемые ЕГЭ (логика, алгоритмизация, обработка массивов/файлов, оценка сложности, моделирование состояний), естественно «встраиваются» в реактивный подход. В конце даны 5 упражнений формата «условие + критерии проверки + подсказки», уместных для тренировки школьника.
Событие – атомарный факт вида ⟨значение, метка времени⟩, возникающий в дискретный момент.
Поток (stream) – (возможно, бесконечная) упорядоченная последовательность событий с неубывающими метками времени.
Наблюдатель (observer) – объект, принимающий события потока (значение, сигнал завершения, сигнал ошибки).
Оператор – чистая трансформация потоков: F: Stream<A> → Stream<B>.
Граф данных (dataflow-graph) – ориентированный ациклический граф операторов, задающий зависимость выходов от входов.
Семантика времени – правила интерпретации меток времени: реальное (стен-часы), логическое (порядок поступления), симулируемое (виртуальное).
Горячий поток – существует независимо от подписки (например, системные события).
Холодный поток – порождается для каждого наблюдателя заново (например, чтение файла).
Реактивное программирование – это push-модель: источник «толкает» данные к подписчикам. Важно различать её с pull-моделью (итераторы/цикл чтения), где потребитель «тянет» данные по мере необходимости.
Пусть S = (e1, e2, …), где ei = ⟨vi, ti⟩, ti ≤ ti+1. Определим базовые операторы:
map: для чистой функции f
map_f(S) = (⟨f(v1), t1⟩, ⟨f(v2), t2⟩, …)
Свойства: сохранение порядка и меток времени; линейная сложность по числу событий.
filter: для предиката p
filter_p(S) = (⟨vi, ti⟩ ∈ S | p(vi) = true)
Свойства: монотонность по включению множества событий.
reduce/scan (префиксная агрегация):
scan_⊕(S, v0) = (⟨v0 ⊕ v1, t1⟩, ⟨(v0 ⊕ v1) ⊕ v2, t2⟩, …)
где ⊕ – ассоциативная операция.
merge: слияние двух потоков по времени с сохранением стабильного порядка при равных метках.
zip: синхронное совмещение парой: берутся по одному событию из каждого входа; метки – согласованные.
window/slide: разбивка потока на окна по времени (Δt) или по количеству (k) событий с последующей агрегацией внутри окон.
Сигналы завершения и ошибок формируют расширенную алгебру событий: к значениям добавляются onComplete и onError. Любой оператор обязан корректно транслировать конечные сигналы (завершение/ошибка) downstream-наблюдателям, что гарантирует безутечность подписок и детерминированное поведение.

Хотя в кодификаторе ЕГЭ реактивное программирование как отдельная тема не выделяется, его навыковые элементы прямо соответствуют экзаменационным умениям:
Практический вывод: отрабатывая filter/map/reduce над строками, числами, датами и моделируя оконные агрегаты, школьник одновременно решает стандартные подзадачи ЕГЭ: подсчёт, поиск, свёртка, анализ логических условий, трассировка состояний.
Фильтрация и преобразование
input: Stream<Int> S
output: Stream<Int> T
T = S
.filter(x -> x is even) // оставить чётные
.map(x -> x * x) // возвести в квадрат
.distinct() // удалить дубликаты
Сложность O(n) по числу событий, память – O(u) для множества уникальных значений (u – число различных элементов в окне наблюдения).
Оконная сумма за последние 5 секунд
input: Stream<Event{value:Int, t:Time}> S
output: Stream<Int> Sum
Sum = S
.window(time = 5s, slide = 1s)
.map(window -> sum(window.values))
Сложность по времени O(n), по памяти – O(λ * 5s), где λ – интенсивность событий.
Управление скоростью (backpressure)
input: Stream<Int> Fast
output: Stream<Int> Sampled
Sampled = Fast.sample(every = 100ms) // взять по значению каждые 100 мс
Гарантирует, что потребитель не будет перегружен.
Упражнение 1. «Фильтр-подсчёт»
Условие. Дан поток целых чисел S. Требуется получить поток пар ⟨t, c⟩, где c – количество чисел, кратных 3, пришедших за последние 10 секунд к каждому моменту времени t (скользящее окно, шаг 1 секунда).
Критерии проверки. Правильная интерпретация окна [t−10; t), корректная инкрементальная поддержка суммы (без полного пересчёта).
Подсказки. Используйте оконный оператор и структуру «очередь событий в окне», удаляя устаревшие.
Упражнение 2. «Логическая фильтрация»
Условие. Поток сообщений M содержит события с полями type ∈ {INFO, WARN, ERROR}, userId, payload. Требуется получить поток A, в котором:
оставлены только события ERROR от нечётных userId;
два одинаковых подряд payload должны сливаться в одно (устранение дребезга по значению с debounce 0 сек, но с условием «подряд»).
Критерии проверки. Корректное применение предикатов (логика), стабильное упорядочение, устранение повторов только для соседних значений.
Подсказки. Сначала filter по типу и предикату нечётности, затем оператор distinctUntilChanged по payload.
Упражнение 3. «Слияние источников и приоритет»
Условие. Даны два потока цен: Market (биржевой) и Local (локальный кэш). Нужно построить поток котировок Q, который:
сначала немедленно выдаёт последнее известное значение из Local (если есть),
затем переключается на Market и выдаёт только возрастающую по времени последовательность цен,
при равных метках времени берёт значение из Market.
Критерии проверки. Правильная инициализация из холодного источника, корректный concat и правило при равных метках.
Подсказки. Используйте concat(local.take(1), market) и стабильно разрешайте конфликты по источнику.
Упражнение 4. «Backpressure-политика»
Условие. Поток датчика поступает с частотой ~5 000 событий/с, а обработчик способен обрабатывать не более 500/с. Спроектируйте три альтернативные цепочки:
a) буферизация с ограничением размера B и сигналом ошибки при переполнении;
b) отбрасывание лишних событий, сохраняя равномерную репрезентативность;
c) сохранение только последнего значения для каждого интервала 10 мс.
Критерии проверки. Аргументация выбора, правильность стратегий buffer, sample/throttle, latest.
Подсказки. Оцените память: O(B) для (a); анализ потерь информации для (b); максимальная свежесть для (c).
Упражнение 5. «Оконная статистика для подготовки к ЕГЭ»
Условие. Дан поток чисел (аналог чтения длинного файла). Требуется для каждого окна из 100 подряд идущих чисел вычислить тройку характеристик: медиану, моду и размах, формируя поток кортежей.
Критерии проверки. Корректная поддержка скользящего окна размера 100, оценка сложности и структуры данных (две кучи для медианы, частотный словарь для моды, деки для min/max).
Подсказки. Сведите задачу к классу «скользящих статистик»; оцените асимптотику O(log W) на событие при разумном выборе структур.
Реактивное программирование формализует обработку данных «как они текут», делая зависимости явными, время – первым классом, а преобразования – чистыми и композиционными. В учебной перспективе это не столько «ещё одна технология», сколько метод дисциплинированного мышления: задавайте точные предикаты, формализуйте окна, оценивайте сложность, фиксируйте правила при равных метках времени и документируйте политику ошибок и скорости. Эти же навыки обеспечивают высокую успешность на ЕГЭ: аккуратное чтение условий, построение корректных алгоритмов, владение логикой и оценкой ресурсов. Пять приведённых упражнений – прямой «мост» между реактивной парадигмой и типовыми экзаменационными умениями: фильтрация, свёртка, скользящие статистики, работа с логическими условиями и графами зависимостей. Освоив реактивный подход на абстрактных потоках, ученик увереннее решает классические задачи, где данные приходят из файла, массива или датчика, а критерии правильности – формализуемы и проверяемы.