6. Массивы и итерация

Изучите массивы фиксированного размера, индексацию, обход, копирование и поиск.

Wave Foundation

Несколько значений одного типа

Если вы создаете три оценки отдельно как score1, score2 и score3, как объявления, так и вычисления должны быть изменены при изменении числа. Массивы группируют определенное количество элементов одного типа. Используя циклы, вы можете применять одни и те же правила к каждому элементу.

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

Введите длину в типе

fun main() {
    var scores: array<i32, 3> = [70, 80, 90];

    println("first={}", scores[0]);
    println("second={}", scores[1]);
    println("last={}", scores[2]);
}

Результат выполнения:

first=70
second=80
last=90

i32 в array<i32, 3> — это тип элемента, а 3 — количество элементов. Мы храним 3 целых числа. Это не означает, что количество байтов равно 3. Количество элементов в литерале массива должно соответствовать объявленной длине.

Индексы начинаются с 0. Первый элемент равен 0, последний элемент имеет длину 1. scores[3] — это доступ за пределами области видимости, а не третий элемент.

изменить элемент

fun main() {
    var scores: array<i32, 3> = [70, 80, 90];

    scores[1] = 85;
    scores[0] += 5;

    println("{} {} {}", scores[0], scores[1], scores[2]);
}

Результат выполнения:

75 85 90

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

Итерация по массиву

fun main() {
    var scores: array<i32, 4> = [60, 70, 80, 90];
    var total: i32 = 0;

    for (var index: i32 = 0; index < 4; index += 1) {
        total += scores[index];
    }

    println("total={} average={}", total, total / 4);
}

Результат выполнения:

total=300 average=75

Каждая итерация считывает элемент по другому индексу. Переменная суммы должна быть инициализирована вне итерации. Инициализация его значением 0 каждый раз внутри тела цикла приведет к неверным результатам, например, останется только последний элемент.

Целочисленное деление, используемое для вычисления среднего, отбрасывает дробную часть. Для получения среднего значения с плавающей запятой преобразуйте сумму перед делением. Для больших массивов или больших значений также убедитесь, что тип аккумулятора может представлять сумму.

Агрегирование только некоторых элементов

Фильтрация может быть достигнута путем объединения условных операторов и обхода. Здесь мы подсчитываем количество элементов с оценкой 80 и выше.

fun main() {
    var scores: array<i32, 5> = [60, 80, 90, 75, 100];
    var passed: i32 = 0;

    for (var index: i32 = 0; index < 5; index += 1) {
        if (scores[index] >= 80) {
            passed += 1;
        }
    }

    println("passed={}", passed);
}

Результат выполнения:

passed=3

Значения индекса и элемента должны быть разделены. При проверке index >= 80 сравниваются позиции, а не баллы. Оба могут быть i32, поэтому трудно найти эту семантическую ошибку только на основе типа.

Найдите первое место совпадения

Сначала решите, как отображать результаты, которые не были найдены. В этом примере допустимыми индексами являются значения от 0 до 4, поэтому в качестве маркера отказа мы используем -1.

fun main() {
    var values: array<i32, 5> = [8, 3, 8, 1, 5];
    var target: i32 = 8;
    var found: i32 = -1;

    for (var index: i32 = 0; index < 5; index += 1) {
        if (values[index] == target) {
            found = index;
            break;
        }
    }

    if (found >= 0) {
        println("found at {}", found);
    } else {
        println("not found");
    }
}

Результат выполнения:

found at 0

Первая позиция 0 также является нормальным результатом. Если вы проверите успех с помощью found > 0, вы ошибетесь, поскольку не найдете первый элемент. По той же причине неверно оценивать успех, используя bool вместо cast.

Если вы удалите break, последующие совпадения перезапишут found, предоставив вам позицию последнего совпадения. Поскольку один оператор может изменить контракт функции, в описании «поиска» также должно быть указано, находится ли он в первой или последней позиции.

Копирование элементов массива

Чтобы скопировать значения массива в другое место хранения, вы можете прочитать и присвоить их элемент за элементом. Даже если вы измените один целочисленный элемент после копирования, другой целочисленный элемент не изменится.

fun main() {
    var original: array<i32, 3> = [1, 2, 3];
    var copied: array<i32, 3>;

    for (var index: i32 = 0; index < 3; index += 1) {
        copied[index] = original[index];
    }

    copied[0] = 99;

    println("original={}", original[0]);
    println("copied={}", copied[0]);
}

Результат выполнения:

original=1
copied=99

Если элементы являются указателями, при их копировании копируются их адреса. Он не дублирует отдельную память, на которую они указывают. Это различие имеет значение при управлении собственностью.

Инициализация и эффективный диапазон

Он не предполагает, что все элементы массива, объявленные без начального значения, могут быть прочитаны. Если записан только частичный номер, фактический инициализированный номер должен управляться отдельно. По этой же причине длина, возвращаемая функцией чтения библиотеки, может быть меньше всей емкости буфера.

Поскольку длина массива содержится в типе, она не увеличивается произвольно во время выполнения. Списки байтов, размер которых увеличивается, используют динамическое хранилище, например Buffer. Изменение длины массива требует рассмотрения типа, начального значения, верхнего предела обхода и вычислений, зависящих от этой длины.

Упражнение: максимумы и местоположения

Найдите максимальное значение и позицию, в которой оно впервые появляется в массиве [4, 9, 2, 9, 1]. Если это обычная функция, где все элементы могут быть отрицательными, максимальное значение не должно инициализироваться равным 0.

Полное решение

fun main() {
    var values: array<i32, 5> = [4, 9, 2, 9, 1];
    var maximum: i32 = values[0];
    var position: i32 = 0;

    for (var index: i32 = 1; index < 5; index += 1) {
        if (values[index] > maximum) {
            maximum = values[index];
            position = index;
        }
    }

    println("max={} first={}", maximum, position);
}

Результат выполнения:

max=9 first=1

Возьмите первый элемент в качестве исходной ссылки и сравните со вторым. Начиная с >, положение не меняется, даже если снова появляется то же самое максимальное значение. Измените его на >=, чтобы он стал последней позицией. Любой интерфейс, который может иметь нулевую длину, должен обрабатывать пустой ввод перед чтением первого элемента.