Распространённые операции над списками
Перечислим некоторые операции над списками, имеющиеся в библиотеке языка Котлин:
listOf(…)— создание нового списка.list1 + list2— сложение двух списков, сумма списков содержит все элементы их обоих.list + element— сложение списка и элемента, сумма содержит все элементыlistи дополнительноelementlist.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 склонен ускользать от внимания читателя, затрудняя понимание программы.
Примеры не жизненные какие-то. Для математиков, а не для программистов.
Прочел вашу фразу и вспомнил анекдот:
Урок в первом классе :
Учительница заносит и ставит на стол компьютер.
— Дети сколько компьютеров на столе?
— Один! — хором отвечают дети.
Учительница заносит и ставит на стол еще один компьютер
— Дети сколько компьютеров на столе?
— Два! — хором отвечают дети.
Идя за третьим компьютером учительница бурчит себе под нос :
— с яблоками было легче.