JavaRush /Курсы /Kotlin SELF /Проход по массиву: сумма, поиск, максимум и подсчёт

Проход по массиву: сумма, поиск, максимум и подсчёт

Kotlin SELF
7 уровень , 3 лекция
Открыта

1. Проход по массиву — ключевой навык

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

В Kotlin цикл for умеет перебирать элементы массива напрямую: for (x in a) { ... } — это нормальный и ожидаемый стиль. Но иногда нам нужен не только элемент, а ещё и его позиция (индекс). Тогда мы идём по indices: for (i in a.indices) { ... }.

Два стиля обхода: по элементам и по индексам

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

Давайте начнём с самого простого: сделаем массив шагов за неделю и просто выведем его, чтобы глазами увидеть данные (программист без вывода в консоль — как повар без ложки).

fun main() {
    val steps: IntArray = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    for (x in steps) {
        print("$x ")
    }
    println() // 4200 5600 3100 8000 7600 9000 1200
}

Обрати внимание: мы сейчас идём по значениям (x in steps). Индексов не видим, x последовательно принимает все значения массива (по одному за раз).

2. Паттерн №1: сумма

Сумма — самый популярный алгоритм в мире начинающих программистов, потому что это первый случай, когда ты понимаешь: цикл — это не «семь раз вывести println», а «семь раз сделать что-то важное». Мы заводим специальную переменную (обычно sum), даём ей стартовое значение (обычно 0), и каждый шаг прибавляем очередной элемент массива.

С точки зрения логики это выглядит как маленькая блок‑схема: инициализировали сумму, прошли по всем элементам, обновили сумму, получили результат. В реальной разработке такие схемы полезны, когда мозг пытается перепрыгнуть через некоторые шаги и случайно забывает инициализацию или обновление.

flowchart TD
    A[sum = 0] --> B{есть следующий элемент?}
    B -->|да| C[sum = sum + x]
    C --> B
    B -->|нет| D[вывести sum]

Теперь реализуем это в виде небольшого проекта StepStats.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var sum = 0
    for (x in steps) {
        sum = sum + x
    }

    println("Total steps = $sum") // Total steps = 38500
}

Важный нюансы: sum — это var, потому что мы её меняем. Если вы попытаетесь сделать val sum = 0, компилятор вас остановит (и будет прав).

3. Среднее: сумма и аккуратное деление

Среднее значение почти всегда строится на сумме. Сначала мы считаем сумму, потом делим на количество элементов. И вот здесь Kotlin, как хороший язык программирования, напоминает: деление Int / Int даст Int, то есть дробная часть пропадёт. Это не баг, это суровая математика целых чисел.

Поэтому, если вы хотите среднее как число с дробью, нужно «перевести» хотя бы одну часть выражения в Double. Самый простой подход: sum.toDouble().

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var sum = 0
    for (x in steps) sum = sum + x

    val avg = sum.toDouble() / steps.size
    println("Average steps = $avg") // Average steps = 5500.0
}

Заметь: steps.size — это количество элементов. Если массив из 7 дней — будет 7.

И да, получилось 5500.0, хотя вроде «красиво» могло быть 5500. Это нормально: Double всегда показывается как число с точкой.

4. Паттерн №2: подсчёт с условием

Подсчёт с условием очень похож на простое суммирование, только вместо «прибавляю значение элемента» мы «прибавляем 1, если условие истинно». Этот паттерн встречается везде: сколько дней было выше нормы, сколько оценок — пятёрки, сколько элементов равны нулю, сколько символов — буквы.

Сделаем в StepStats задачу: посчитать, сколько дней было не меньше 7000 шагов (наша цель).

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var goodDays = 0
    for (x in steps) {
        if (x >= 7000) 
            goodDays = goodDays + 1
    }

    println("Good days = $goodDays") // Good days = 3
}

Тут мы снова используем обход по элементам, потому что нас интересует сам факт (значение), а не позиция.

Теперь сделаем чуть более жизненный вариант: пользователь вводит некоторое значение (порог), а мы считаем, сколько дней выше порога.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    print("Enter threshold: ")
    val threshold = readln().toInt()

    var count = 0
    for (x in steps) 
        if (x >= threshold) count++

    println("Days >= $threshold : $count")
}

Обрати внимание: count++ — это короткая форма «увеличь на 1». Очень удобно, особенно в счётчиках.

5. Паттерн №3: поиск и индекс

Поиск — это когда мы хотим выяснить: «есть ли в массиве значение X?» или «на какой позиции находится первое значение X?». Тут почти всегда нужны индексы, потому что найти значение часто недостаточно — важно ещё знать, где оно лежит.

А вот если ничего не нашли — возвращаем -1. Почему -1? Потому что это значение точно не может быть корректным индексом массива (корректные начинаются с 0).

И ещё одна важная деталь: если мы ищем первое совпадение, то после того как нашли — можно остановиться через break.

Давайте начнем с поиска значения в массиве шагов: найдём, был ли день ровно с 9000 шагов, и если был — какой у него индекс.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)
    val target = 9000

    var index = -1
    for (i in steps.indices) {
        if (steps[i] == target) 
            { index = i; break }
    }

    println("Index of $target = $index") // Index of 9000 = 5
}

Вот здесь самое время использовать steps.indices — диапазон индексов массива. Это помогает не думать про «последний индекс = size - 1».

Давайте сделаем еще один более практичный пример. Например, найти первый день, когда шагов было меньше некоторого минимума (например, меньше 3000).

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var badIndex = -1
    for (i in steps.indices) {
        if (steps[i] < 3000) 
            { badIndex = i; break }
    }

    println("First day with < 3000 steps: $badIndex") // First day with < 3000 steps: 6
}

Если хочется, можно добавить текст «не найдено» (когда badIndex == -1), но пока нам важен сам паттерн.

6. Паттерн №4: максимум

Максимум — это тоже очень интересный паттерн. Новички тут часто ловятся на «а давайте присвоим max нулю». Иногда это работает, а иногда — нет.

Давайте найдём максимальное значение в массиве.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var max = 0
    for (x in steps) {
        if (x > max) 
            max = x
    }

    println("Max steps = $max") // Max steps = 9000
}

Все работает. И работает правильно. Вот только тут есть логическая ошибка.

Если массив будет содержать отрицательные числа, то max = 0 ломает всю логику: вы получите максимум 0 даже если его в массиве нет. Правильное правило универсальное: если ищем максимум, то нужно начинать с первого элемента массива.

Вот как правильно искать максимальное число шагов.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var max = steps[0]		// max равен первому значению массива
    for (x in steps) {
        if (x > max) 
            max = x
    }

    println("Max steps = $max") // Max steps = 9000
}

Из этого следует важный вывод: чтобы брать a[0], массив должен быть непустой: size > 0. Поэтому или мы заранее знаем, что массив не пуст, или обязаны написать проверку if (a.size == 0).

А теперь давайте попробуем найти не только максимум, а и день когда пользователь поставил рекорд. Тут нам снова нужны индексы.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var maxIndex = 0				// индекс стартового значния
    var max = steps[maxIndex]		// стартовое значение `max`

    for (i in steps.indices) {
        if (steps[i] > max) 
            { max = steps[i]; maxIndex = i }
    }

    println("Max = $max at index $maxIndex") // Max = 9000 at index 5
}

Обратите внимание на стиль: мы обновляем и значение, и индекс вместе. Если обновить только одно — получится «максимум от одного дня, а индекс от другого», так нельзя.

7. Добавляем бизнес-логику

Теперь сделаем самый приятный момент: возьмём уже знакомые паттерны и соберём маленький «отчёт за неделю». Важно не пытаться сделать «всё в одном цикле» любой ценой: новичку проще читать два-три понятных цикла, чем один цикл‑монстр.

Сделаем так: посчитаем сумму, среднее, максимум и количество дней выше 7000 — и красиво выведем.

fun main() {
    val steps = intArrayOf(4200, 5600, 3100, 8000, 7600, 9000, 1200)

    var sum = 0
    var goodDays = 0
    var max = steps[0]

    for (x in steps) {
        sum += x
        if (x >= 7000) goodDays++
        if (x > max) max = x
    }

    val avg = sum.toDouble() / steps.size
    println("Total = $sum, avg = $avg, max = $max, goodDays = $goodDays")
    // Total = 38500, avg = 5500.0, max = 9000, goodDays = 3
}

Ниже небольшая табличка, чтобы зафиксировать, чем похожи паттерны и чем отличаются:

Паттерн Что хранит переменная‑результат Стартовое значение Что происходит в цикле
Сумма сумма элементов (sum) 0 sum += x
Подсчёт количество (count) 0 if (cond) count++
Поиск индекс (index) -1 при совпадении index = i и часто break
Максимум максимальное значение (max) a[0] (только если массив не пуст) if (x > max) max = x

Пустой массив: всегда есть подвох

Пустой массив — это ситуация, когда size == 0. Иногда он появляется специально (например, «нет данных»), иногда случайно (например, вы создали IntArray(n), а n оказалось 0).

С пустым массивом почти все паттерны работают нормально, кроме тех, где вы берёте a[0] (максимум, минимум, иногда старт для чего-то ещё).

Поэтому правило простое: если ваш алгоритм требует обратиться к первому элементу, то до этого должна быть проверка, что массив не пуст.

Вот безопасный шаблон для поиска максимума:

fun main() {
    val steps = intArrayOf() // пусто

    if (steps.size == 0) {
        println("No data") // No data
    } else {
        var max = steps[0]
        for (x in steps) if (x > max) max = x
        println("Max = $max")
    }
}

Этот пример не делает ничего умного, он просто честно обрабатывает случай когда данных нет.

8. Типичные ошибки при проходах по массиву

Ошибка №1: считать среднее как sum / size и удивляться, почему оно без дробной части.
Если sum и size — это Int, то и результат будет Int. Поэтому при вычислении среднего нужно переводить сумму (или размер) в Double, например sum.toDouble() / size.

Ошибка №2: начинать максимум с 0 «потому что так проще».
Иногда это случайно работает (если все числа неотрицательные), но как только появятся отрицательные значения — логика рушится. Правильный старт для максимума — первый элемент массива a[0], но только в ветке, где массив точно непустой.

Ошибка №3: забыть про break в поиске и получить «последний найденный индекс» вместо «первого».
Самый распространённый вариант: поиск написан, но break не сделан. В итоге цикл продолжает идти, и индекс перезаписывается снова и снова.

Ошибка №4: путать обход по элементам и по индексам, а потом не понимать, почему «не получается получить позицию».
for (x in a) не даёт вам индекс — только значение. Если вы хотите индекс, используйте for (i in a.indices) и берите значение как a[i].

Ошибка №5: использовать неправильные границы индексов и случайно выходить за массив.
Классика: написать for (i in 0..a.size) и получить выход за границы на последнем шаге. Самый надёжный вариант — a.indices, потому что он ровно «про корректные индексы».

Ошибка №6: не инициализировать переменную‑накопитель или инициализировать «не тем».
Для суммы старт должен быть 0. Для подсчёта тоже 0. Для поиска индекс удобно стартовать с -1. Если перепутать стартовые значения, алгоритм будет давать странные результаты — иногда даже «почти правильные», что особенно коварно.

Ошибка №7: пытаться решить задачу «в одну строку» и потерять читаемость.
Новички иногда делают поиск, сумму, максимум и печать в одной гигантской конструкции, и потом сами не могут объяснить, что происходит. На этом этапе важнее, чтобы код читался линейно: инициализация → цикл → обновления → результат.

1
Задача
Kotlin SELF, 7 уровень, 3 лекция
Недоступна
Чек за день
Чек за день
1
Задача
Kotlin SELF, 7 уровень, 3 лекция
Недоступна
Чётные и нечётные
Чётные и нечётные
1
Задача
Kotlin SELF, 7 уровень, 3 лекция
Недоступна
Поиск товара
Поиск товара
1
Задача
Kotlin SELF, 7 уровень, 3 лекция
Недоступна
Максимум партии
Максимум партии
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ