Массивы, скользящее среднее, фильтр выбросов

Одно показание датчика — случайность. Несколько показаний подряд — уже картина. Сегодня научим станцию копить показания и считать по ним среднее, находить минимум/максимум и замечать выброс — по одному показанию за раз, «на лету».

Ряд ячеек с числами показаний и график скользящего среднего рядом

После урока сможешь: объявлять массив и перебирать его циклом, считать сумму/среднее/минимум/максимум, вести скользящее окно последних N показаний по кругу, находить выброс. В конце — станция со скользящим средним по потоку показаний.

Вспомни: в Python-курсе ты уже собирал «Анализ лога датчика» — список показаний, sum/min/max, фильтрация (занятие 5 первого курса). Массив в C++ — тот же список, но с фиксированным размером, заданным заранее, и индексация тоже начинается с 0.

Массив: набор значений под одним именем

Массив хранит несколько значений одного типа под одним именем, доступ — по индексу в квадратных скобках. Размер задаётся один раз и не меняется.

float pokazaniya[5] = {22.0, 23.0, 21.0, 24.0, 22.0};
Serial.println(pokazaniya[0]);
Serial.println(pokazaniya[2]);
22.00
21.00
  Массив pokazaniya[5] — пять ячеек, индексы 0..4:

  индекс:    0      1      2      3      4
           +------+------+------+------+------+
  значение: | 22.0 | 23.0 | 21.0 | 24.0 | 22.0 |
           +------+------+------+------+------+

  pokazaniya[0] -> 22.0   (первый элемент — индекс 0, не 1)
  pokazaniya[4] -> 22.0   (последний элемент массива размером 5 — индекс 4)

Перебор циклом: сумма и среднее

Как и с списком в Python, массив перебирают циклом for по индексу от 0 до размера минус один.

float pokazaniya[5] = {22.0, 23.0, 21.0, 24.0, 22.0};
float summa = 0;
for (int i = 0; i < 5; i = i + 1) {
  summa = summa + pokazaniya[i];
}
float srednee = summa / 5;
Serial.println(srednee);
22.40

Минимум и максимум без готовых функций

В C++ для массива нет готовых min()/max(), как sum()/min()/max() у списка в Python — их пишут циклом самостоятельно: берут первый элемент как «пока лучший» и сравнивают с остальными.

float pokazaniya[5] = {22.0, 23.0, 21.0, 24.0, 22.0};
float minimum = pokazaniya[0];
float maksimum = pokazaniya[0];
for (int i = 1; i < 5; i = i + 1) {
  if (pokazaniya[i] < minimum) {
    minimum = pokazaniya[i];
  }
  if (pokazaniya[i] > maksimum) {
    maksimum = pokazaniya[i];
  }
}

Скользящее окно: обновляем по кругу

Станция работает непрерывно — новые показания приходят постоянно, а массив фиксированного размера. Решение: скользящее окно — новое показание записывается на место САМОГО СТАРОГО, а индекс записи движется по кругу. Для этого используют остаток от деления %: как только индекс доходит до конца массива, % возвращает его в начало.

Представь еженедельник на 5 страниц — по числу рабочих дней. Когда наступает новый понедельник, ты не заводишь новую страницу: ты стираешь запись прошлого понедельника и пишешь поверх неё. Так же работает окно из 5 ячеек — шестое показание не добавляется отдельно, а перезаписывает самое старое.

const int RAZMER_OKNA = 5;
float okno[RAZMER_OKNA];
int indeks = 0;

void zapisatPokazanie(float novoe) {
  okno[indeks] = novoe;
  indeks = (indeks + 1) % RAZMER_OKNA;
}
  Круговая запись индекса (RAZMER_OKNA = 5):

  indeks:  0 -> 1 -> 2 -> 3 -> 4 -> (0+1)%5=0 -> 1 -> ...

  На шаге, где indeks=4: indeks=(4+1)%5 = 5%5 = 0 -- вернулись в начало,
  следующая запись перезапишет САМЫЙ СТАРЫЙ элемент (индекс 0)

Фильтр выбросов: fabs() и порог

Выброс — показание, которое сильно отличается от среднего по окну. Модуль разности дробных чисел считает fabs(a - b) (f — от float, как и в других именах этой библиотеки). Если модуль разности больше порога — это выброс.

float srednee = 22.4;
float novoe_pokazanie = 45.0;
float porog = 5.0;

if (fabs(novoe_pokazanie - srednee) > porog) {
  Serial.println("Выброс");
} else {
  Serial.println("Норма");
}
Выброс

Миссия: скользящее среднее по потоку

Подключи потенциометр к A0. Каждые 500 мс (через millis, без delay) читай показание, записывай в скользящее окно из 5 ячеек по кругу, считай среднее по окну и печатай его вместе с новым показанием.

const int RAZMER_OKNA = 5;
float okno[RAZMER_OKNA];
int indeks = 0;
unsigned long previousMillis = 0;
const long interval = 500;

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < RAZMER_OKNA; i = i + 1) {
    okno[i] = 0;
  }
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    float novoe = map(analogRead(A0), 0, 1023, 0, 100);
    okno[indeks] = novoe;
    indeks = (indeks + 1) % RAZMER_OKNA;

    float summa = 0;
    for (int i = 0; i < RAZMER_OKNA; i = i + 1) {
      summa = summa + okno[i];
    }
    float srednee = summa / RAZMER_OKNA;

    Serial.print("Показание: ");
    Serial.print(novoe);
    Serial.print(" | Среднее по окну: ");
    Serial.println(srednee);
  }
}

Проверь себя

Контрольный вопрос. Что такое массив?

АГотовая библиотека функций для работы с датчиками
БСпособ вести время без остановки программы
ВНабор значений одного типа под одним именем с доступом по индексу

Подсказка: Это разбиралось в блоке «Массив: набор значений под одним именем».

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. С какого числа начинается индексация элементов массива?

АС размера массива
БС 0
ВС 1

Подсказка: Так же, как индексация списка в Python.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Что выведет программа?

Исходный код для этого задания:

float pokazaniya[4] = {10.0, 20.0, 30.0, 40.0};
Serial.println(pokazaniya[2]);
А10
Б20
В30

Подсказка: Индекс 2 — это третий по счёту элемент (0, 1, 2).

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Массив объявлен как float pokazaniya[5]. Какой индекс у последнего элемента?

А5
Б0
В4

Подсказка: Индексы идут от 0, значит последний индекс — это размер минус один.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Чему равно среднее значение этого массива? Впиши число (без дробной части, если оно целое).

Исходный код для этого задания:

float pokazaniya[4] = {10.0, 20.0, 30.0, 40.0};
// среднее = сумма всех элементов / количество элементов

Подсказка: Сложи все четыре числа и раздели на количество элементов массива.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Массив объявлен как float pokazaniya[5]. В цикле перебора написано условие i <= 5 вместо i < 5. К чему это приведёт?

Исходный код для этого задания:

for (int i = 0; i <= 5; i = i + 1) {
  summa = summa + pokazaniya[i];
}
АКомпилятор сам исправит границу цикла до 5
БЦикл обратится к pokazaniya[5] — индексу за пределами массива (его не существует), поведение непредсказуемо
ВНичего страшного — массив просто добавит ячейку 5 автоматически

Подсказка: У массива размером 5 индексы только 0..4 — индекса 5 не существует.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. indeks равен 3, RAZMER_OKNA равен 5. Чему станет равен indeks после (indeks + 1) % RAZMER_OKNA? Впиши число.

Подсказка: Сначала посчитай indeks + 1, потом раздели на RAZMER_OKNA и возьми остаток.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Задание. Напиши код сам. Допиши недостающую часть цикла вместо ___, чтобы summa накопила сумму всех элементов массива pokazaniya размером 5.

Исходный код для этого задания:

float summa = 0;
for (int i = 0; i < 5; i = i + 1) {
  summa = summa + ___;
}

Подсказка: Обратись к элементу массива по текущему индексу цикла i.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Задание. Напиши код сам (2 строки внутри функции записи). Есть массив okno, индекс indeks и размер RAZMER_OKNA. Запиши значение novoe в текущую позицию окна, затем сдвинь indeks на следующий по кругу.

Исходный код для этого задания:

void zapisatPokazanie(float novoe) {
  // напиши: записать novoe в okno[indeks], затем сдвинуть indeks по кругу
}

Подсказка: Первая строка — присваивание в ячейку массива по индексу; вторая — indeks = (indeks + 1) % RAZMER_OKNA.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Онлайн-проверка ответа появится позже

Контрольный вопрос. Окно размером 3: okno = {10, 20, 30}, indeks = 0 (следующая запись пойдёт в ячейку 0). Записали новое показание 99. Каким стал массив okno после записи? Впиши три числа через запятую в порядке индексов 0,1,2.

Исходный код для этого задания:

// okno[0]=10, okno[1]=20, okno[2]=30, indeks=0
// zapisatPokazanie(99);

Подсказка: Новое значение перезаписывает ячейку по ТЕКУЩЕМУ indeks (0), остальные ячейки не меняются.

Онлайн-проверка ответа появится позже

Задание. Напиши код сам (цикл, около 8 строк). Найди минимум и максимум массива pokazaniya размером 5 без готовых функций min/max — используй первый элемент как стартовое значение обеих переменных.

Исходный код для этого задания:

float pokazaniya[5] = {22.0, 23.0, 21.0, 24.0, 22.0};
// напиши поиск minimum и maksimum циклом

Подсказка: Начни minimum и maksimum со значения pokazaniya[0], затем в цикле от i=1 сравнивай каждый следующий элемент и обновляй при необходимости.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Онлайн-проверка ответа появится позже

Задание. Мини-проект. Дан массив okno из 5 сохранённых показаний {22.0, 23.0, 21.0, 24.0, 22.0} и новое показание novoe_pokazanie = 45.0, порог porog = 5.0. Напиши код (около 8 строк), который: (1) циклом посчитает среднее по okno, (2) сравнит novoe_pokazanie со средним через fabs(), (3) если разница больше porog — выведет «Выброс», иначе «Норма», (4) выведет и само посчитанное среднее. Принято, если: среднее по этим числам посчитано верно (22.4), разница с novoe_pokazanie сравнена именно с porog через fabs(), и по этим данным программа печатает «Выброс».

Исходный код для этого задания:

float okno[5] = {22.0, 23.0, 21.0, 24.0, 22.0};
float novoe_pokazanie = 45.0;
float porog = 5.0;
// напиши расчёт среднего, сравнение и вывод по описанию выше

Подсказка: Сначала цикл суммирования и деления на 5 (как в блоке «Перебор циклом»), затем if (fabs(novoe_pokazanie — srednee) > porog) с выводом «Выброс»/«Норма» в каждой ветке, и отдельная строка с выводом srednee.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Онлайн-проверка ответа появится позже

Что дальше

Теперь станция копит показания, считает среднее, минимум/максимум и находит выброс. Код при этом становится длинным и повторяющимся. На следующем занятии разберём функции — как вынести повторяющиеся куски в отдельные именованные блоки, и соберём читаемый текстовый дашборд.

Итог: ты работаешь с массивами, считаешь сумму/среднее/минимум/максимум циклом, ведёшь скользящее окно по кругу и находишь выброс через fabs(). Дальше — функции и структурированный дашборд.

Назад  ·  ↑ В начало урока  ·  ⌂ В начало курса  ·  Вперёд →

Школа Виктора Комлева