Распространённые операции над списками
Перечислим некоторые операции над списками, имеющиеся в библиотеке языка Котлин:
listOf(…)
— создание нового списка.list1 + list2
— сложение двух списков, сумма списков содержит все элементы их обоих.list + element
— сложение списка и элемента, сумма содержит все элементыlist
и дополнительноelement
list.size
— получение размера списка (Int).list.isEmpty()
,list.isNotEmpty()
— получение признаков пустоты и непустоты списка (Boolean).list[i]
— индексация, то есть получение элемента списка с целочисленным индексом (номером)i
. По правилам Котлина, в списке изn
элементов они имеют индексы, начинающиеся с нуля: 0, 1, 2, …, последний элемент списка имеет индексn - 1
. То есть, при использовании записиlist[i]
должно быть справедливоi >= 0 && i < list.size
. В противном случае выполнение программы будет прервано с ошибкой (использование индекса за пределами границ списка).list.sublist(from, to)
— создание списка меньшего размера (подсписка), в который войдут элементы спискаlist
с индексамиfrom
,from + 1
, …,to - 2
,to - 1
. Элемент с индексомto
не включается.element in list
— проверка принадлежности элементаelement
спискуlist
.for (element in list) { … }
— цикл for, перебирающий все элементы спискаlist
.list.first()
— получение первого элемента списка (если список пуст, выполнение программы будет прервано с ошибкой).list.last()
— получение последнего элемента списка (аналогично).list.indexOf(element)
— поиск индекса элементаelement
в спискеlist
. Результат этой функции равен -1, если элемент в списке отсутствует. В противном случае, при обращении к спискуlist
по вычисленному индексу мы получимelement
.list.min()
,list.max()
— поиск минимального и максимального элемента в списке.list.sum()
— сумма элементов в списке.list.sorted()
,list.sortedDescending()
— построение отсортированного списка (по возрастанию или по убыванию) из имеющегося.list1 == list2
— сравнение двух списков на равенство. Списки равны, если равны их размеры и соответствующие элементы.
Мутирующие списки
Мутирующий список является разновидностью обычного, его тип определяется как MutableList<ElementType>
. В дополнение к тем возможностям, которые есть у всех списков в Котлине, мутирующий список может изменяться по ходу выполнения программы или функции. Это означает, что мутирующий список позволяет:
- Изменять своё содержимое операторами
list[i] = element
. - Добавлять элементы в конец списка, с увеличением размера на 1:
list.add(element)
. - Удалять элементы из списка, с уменьшением размера на 1 (если элемент был в списке):
list.remove(element)
. - Удалять элементы из списка по индексу, с уменьшением размера на 1:
list.removeAt(index)
. - Вставлять элементы в середину списка:
list.add(index, element)
— вставляет элементelement
по индексуindex
, сдвигая все последующие элементы на 1, напримерlistOf(1, 2, 3).add(1, 7)
даст результат[1, 7, 2, 3]
.
Для создания мутирующего списка можно использовать функцию mutableListOf(…)
, аналогичную listOf(…)
.
Рассмотрим пример. Пусть имеется исходный список целых чисел list
. Требуется построить список, состоящий из его отрицательных элементов, порядок их в списке должен остаться прежним. Для этого требуется:
- создать пустой мутирующий список
- пройтись по всем элементам исходного списка и добавить их в мутирующий список, если они отрицательны
- вернуть заполненный мутирующий список
fun negativeList(list: List<Int>): List<Int> { val result = mutableListOf<Int>() for (element in list) { if (element < 0) { result.add(element) } } return result }
Здесь промежуточная переменная result
имеет тип MutableList<Int>
(убедитесь в этом в IDE с помощью комбинации Ctrl+Q
). Несмотря на это, мы можем использовать её в операторе return функции с результатом List<Int>
. Происходит это потому, что тип MutableList<Int>
является разновидностью типа List<Int>
, то есть, любой мутирующий список является также и просто списком (обратное неверно — не любой список является мутирующим). На языке математики это означает, что ОДЗ (область допустимых значений) типа MutableList<Int>
является подмножеством ОДЗ типа List<Int>
.
В следующем примере функция принимает на вход уже мутирующий список целых чисел, и меняет в нём все положительные числа на противоположные по знаку:
fun invertPositives(list: MutableList<Int>) { for (i in 0 until list.size) { val element = list[i] if (element > 0) { list[i] = -element } } }
Функция invertPositives
не имеет результата. Это ещё один пример функции с побочным эффектом, которые уже встречались нам в первом уроке. Единственный смысл вызова данной функции — это изменение мутирующего списка, переданного ей как аргумента.
Обратите внимание на заголовок цикла for. Здесь мы вынуждены перебирать не элементы списка, а их индексы, причём запись i in 0 until list.size
эквивалентна i in 0..list.size - 1
(использование until
несколько лучше, так как позволяет избежать лишнего вычитания единицы). Прямой перебор элементов списка в данном примере не проходит:
fun invertPositives(list: MutableList<Int>) { for (element in list) { if (element > 0) { element = -element // Val cannot be reassigned } } }
Параметр цикла for является неизменяемым. Записать здесь list[i] = -element
тоже не получится, так как индекс i
нам неизвестен. Возможна, правда, вот такая, чуть более хитрая запись, перебирающая элементы и индексы одновременно:
fun invertPositives(list: MutableList<Int>) { for ((index, element) in list.withIndex()) { if (element > 0) { list[index] = -element } } }
Использованная здесь функция list.withIndex()
из исходного списка формирует другой список, содержащий пары(индекс, элемент), а цикл for((index, element) in …)
перебирает параллельно и элементы и их индексы. О том, что такое пара и как ей пользоваться в Котлине, мы подробнее поговорим позже.
В общем и целом, редко когда стоит пользоваться функциями, основной смысл которых заключается в изменении их параметров. Посмотрите, например, как выглядит тестовая функция для invertPositives
:
fun invertPositives() { val list1 = mutableListOf(1, 2, 3) invertPositives(list1) assertEquals(listOf(-1, -2, -3), list1) val list2 = mutableListOf(-1, 2, 4, -5) invertPositives(list2) assertEquals(listOf(-1, -2, -4, -5), list2) }
Если ранее у нас одна проверка всегда занимала одну строку, то в этом примере она занимает три строки из-за необходимости создания промежуточных переменных list1
и list2
. Кроме этого, факт изменения list1
, list2
при вызове invertPositives
склонен ускользать от внимания читателя, затрудняя понимание программы.
Примеры не жизненные какие-то. Для математиков, а не для программистов.
Прочел вашу фразу и вспомнил анекдот:
Урок в первом классе :
Учительница заносит и ставит на стол компьютер.
— Дети сколько компьютеров на столе?
— Один! — хором отвечают дети.
Учительница заносит и ставит на стол еще один компьютер
— Дети сколько компьютеров на столе?
— Два! — хором отвечают дети.
Идя за третьим компьютером учительница бурчит себе под нос :
— с яблоками было легче.