Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

List (Списки)

Класс List входит в состав стандартной библиотеки и не требует явного импорта. Списки являются частью коллекций и служат контейнером для объектов.

Зачем нужны списки

У сильной независимой женщины Наташи проживает сорок (!) котов. Аккуратная хозяйка решила записать имена котов в тетрадку.

Мои ненаглядные коты

  • Мурзик
  • Рыжик
  • Барсик
  • И так далее...

Наташа немного знакома с программированием и решила вести учёт в электронном виде, написав собственную программу под Android.

Первоначальный вариант был следующим.


val cat1 = "Мурзик"
val cat2 = "Рыжик"
val cat3 = "Барсик"

println(cat1)
println(cat2)
println(cat3)

В какой-то момент, Наташе надоело писать такой код. Для каждого кота нужно заводить отдельную переменную, при этом сложно следить за порядком, если захочется поменять местами Рыжика и Барсика, сделав их котами под номерами 3 и 2.

К счастью, существуют коллекции. Список является распространённым вариантом коллекции. Создадим список котов через функцию listOf().


val myLovelyCats = listOf( // Переменная Мои ненаглядные коты
    "Мурзик", // первый кот в списке
    "Рыжик",
    "Барсик", // последний кот в списке
)

println(myLovelyCats)

>> [Мурзик, Рыжик, Барсик]

Все элементы списка находятся в том порядке, в котором их заносили.

Работая с коллекциями, нужно обращать внимание на тип коллекции и на тип элементов в коллекции. В нашем случае мы уже определились, что типом коллекции является список. Элементами списка являются строки. Поэтому тип нашей переменной myLovelyCats будет List<String>.

Поговорим о добавлении и удалении элементов из списка. Когда мы создаём список при помощи listOf(), то получаем неизменяемый список. В таком списке нельзя ничего добавить или убавить. Но как же быть, если у Наташи появился новый кот? Мы можем создать новый список, взяв старый список и добавив новый элемент.


val newMyLovelyCats = myLovelyCats + "Васька"

println(newMyLovelyCats)

Нам пришлось создавать второй список при попытке добавить новый элемент. Не слишком удобно, но мы можем кое-что изменить. Список является неизменяемым в любом случае, даже если бы мы объявили переменную как var. Но тем не менее var поможет присвоить значение нового списка уже созданной раннее переменной.


var myLovelyCats = listOf( // Переменная Мои ненаглядные коты
    "Мурзик", // первый кот в списке
    "Рыжик",
    "Барсик", // последний кот в списке
)

myLovelyCats = myLovelyCats + "Васька"

println(myLovelyCats)

Это можно представить как Наташа вырывает лист из блокнота и выбрасывает его, и заново пишет в блокноте старый список, приписывая нового кота.

По такому же принципу мы можем удалять элементы списка, используя минус вместо плюса. Наташа отдала своего Рыжика подружке Тане.


myLovelyCats = myLovelyCats - "Рыжик"

Мы можем прибавлять и отнимать одновременно.


var myLovelyCats = listOf( // Переменная Мои ненаглядные коты
    "Мурзик", // первый кот в списке
    "Рыжик",
    "Барсик", // последний кот в списке
)

myLovelyCats = myLovelyCats + "Васька" - "Рыжик"

println(myLovelyCats)

Но подобный подход расходует слишком много ресурсов, особенно, если список очень большой. Нам приходится создавать второй такой же список, который будет занимать ограниченную память. К счастью, есть альтернативный вариант - изменяемые списки. Изменяемый список MutableList создаётся при помощи функции mutableListOf. Удалять и добавлять элементы в изменяемом списке можно через функции add() и remove().


val myLovelyCats = mutableListOf( // Переменная Мои ненаглядные коты
    "Мурзик", // первый кот в списке
    "Рыжик",
    "Барсик", // последний кот в списке
)

myLovelyCats.add("Васька")
myLovelyCats.remove("Рыжик")

println(myLovelyCats)

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

Научившись создавать разные типы списков, пора понять, как получить элементы списка. Все элементы списка находятся в упорядоченном состоянии и каждому элементу присваивается индекс, который начинается с 0 для первого элемента. С помощью функции get() с номером индекса мы можем получить значение элемента.


// второй элемент списка
println(myLovelyCats.get(1))

Тут главное не переборщить и не указать слишком большое число для индекса. Если у Наташи сорок котов, а вы укажете индекс 42 (ответ на главный вопрос жизни, вселенной и всего такого), то получите ошибку - исключение ArrayIndexOutOfBoundsException.

Можно не вызывать функцию get(), а сразу указать номер индекса в квадратных скобках.


// третий элемент списка
println(myLovelyCats[2])

Следующий важный момент - вывести все элементы на экран. Инструкция println(myLovelyCats) выводит все элементы в одну строку. Но удобнее видеть элементы с каждой новой строки. Можно последовательно выводить элементы следующим образом.


println(myLovelyCats[0])
println(myLovelyCats[1])
println(myLovelyCats[2])

Для небольших списков такой способ не критичен. Но если у нас сорок элементов, то печатать 40 раз похожий код утомительно. У списков есть функция перебора элементов - forEach(), которая сама пройдётся по всем элементам списка.


myLovelyCats.forEach { element ->
    println(element)
}

// сокращённый вариант с использованием ключевого слова it
myLovelyCats.forEach { println(it) }
// или
myLovelyCats.forEach(::println)

Если мы объявляем изменяемый список без инициализации, то обязательно указываем тип данных. Иначе компилятор не поймёт, что мы хотим использовать.


val list = mutableListOf<Int>()
list.add(1)

Функции

Познакомившись с базовыми примерами списка, можно перейти к полезным функциям. Некоторые примеры повторяются.

У многих функций есть суффиксы To, By, With: associate, associateTo, associateBy, associateByTo, associateWith, associateWithTo.

В некоторых примерах используются объекты класса Cat.


data class Cat(
    val name: String,
    val age: Int,
    val weight: Int
)

List

Функции для создания списков. В списках допустимы дубликаты.

listOf()

Неизменяемые списки List создаются через функцию listOf():


val list = listOf(1, 3, 9)
println(list.javaClass)
// class java.util.Arrays$ArrayList

Компилятор определяет тип объекта, который должен содержаться в списке, по типам всех значений, переданных при создании. Например, список в нашем примере инициализируется тремя числами, поэтому компилятор создаёт List с типом List<Int>. Тип List также можно задать явно:


val cats: List<String>
cats = listOf("Барсик", "Мурзик", "Васька")

Если у вас есть изменяемый список, то его можно сконвертировать в неизменяемый список через toList():


val cats = mutableListOf("Мурзик", "Барсик", "Рыжик")
val readOnlyCats = cats.toList()
println("${readOnlyCats.first()}")

Доступ к отдельному элементу списка можно получить по номеру индекса через квадратные скобки.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
println(cats[2])

Также можно использовать метод get() с указанием номера индекса.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
println(cats.get(2))

Но у этих способов есть один недостаток - если вы укажете неправильное значение индекса, то получите исключение ArrayIndexOutOfBoundsException. Вы можете избежать проблемы, если будете использовать метод getOrElse() с указанием значения по умолчанию, если выйдете за пределы допустимых значений. В этом случае вам не придётся обрабатывать исключение.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
println(cats.getOrElse(4) { "Неизвестный котик" })
// или как вариант, имя первого кота
println(cats.getOrElse(4) { cats.first() })

Другой вариант избежать исключения - использовать getOrNull(), который вернёт null при неправильном индексе. Осталось обработать ситуацию.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
val cat = cats.getOrNull(4) ?: "Неизвестный котик"
println(cat)

Размер списка можно узнать через свойство size. Зная размер, можно без опаски пройтись по всем элементам списка.

Пройтись по всем элементам списка можно через for..in.


val cats = listOf("Cat1", "Cat2", "Cat3")
for(cat in cats) print (cat)

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


val list = listOf(1, 2, 3, 4, 5)
// есть ли элемент, который делится без остатка?
println(list.any { it % 2 == 0 }) // true
// есть ли элемент больше 10?
println(list.any { it > 10 }) // false

Выражение !any можно заменить на all.

С помощью all и предиката можно узнать, выполняется ли условие для всех элементов.


val list = listOf(1, 2, 3, 4, 5)
// все элементы меньше 7?
println(list.all { it < 7 }) // true

// всем котам меньше 11 лет?
val cats = listOf(Cat("Barsik", 5, true),
        Cat("Murzik", 9, true))
println(cats.all{it.age < 11}) // true

Выражение !all можно заменить на any.

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


val list = listOf(1, 2, 3, 4, 5)
// все элементы больше 6?
println(list.none { it > 6 }) // true

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


val list = listOf(1, 2, 3, 4, 5)
// сколько элементов меньше 4?
println(list.count { it < 4 }) // 3

// сколько котов младше 11 лет?
val cats = listOf(Cat("Barsik", 5),
        Cat("Murzik", 9))
println(cats.count{it.age < 11}) // 2

Не используйте для этой цели цепочку filter.size, так как вы создаёте промежуточную коллекцию, а это накладные расходы. Функция count считает только количество элементов, а не сами элементы.

fold задаёт начальное значение, а потом собирает все значения в списке от первого к последнему.


val list = listOf(1, 2, 3, 4, 5)

// 15 + 1 + 2 + 3 + 4 + 5
println(list.fold(15) { total, next -> total + next }) // 30

reduce работает аналогично, только без указания начального значения.


println(listOf(1, 2, 3).reduce { total, next ->
    total + next
})

foldRight работает аналогично fold, только значения берутся от последнего к первому.


println(listOf(1, 2, 3).foldRight(1) { total, next -> total * next })

reduceRight похож на foldRight без указания начального значения.

forEach позволяет пройтись по всем элементам списка.


listOf(1, 2, 3).forEach{println(it)}

forEachIndexed работает аналогично, вдобавок мы можем получить индекс позиции и его значение.


listOf("Барсик", "Рыжик", "Васька").forEachIndexed { index, value
    ->
    println("В позиции $index содержится $value")
}

В позиции 0 содержится Барсик
В позиции 1 содержится Рыжик
В позиции 2 содержится Васька

maxOrNull() позволит получить максимальное значение из списка или null при отсутствии элементов. Работает с базовыми типами.


println(listOf(3, 9, 6).maxOrNull())

Функция max() делает тоже самое, но выбрасывает исключение при отсутствии элементов (null).


val numbers = listOf<Int>()
println(numbers.maxOrNull()) // "null"
println(numbers.max()) // Получим исключение

Функция minOrNull() вычисляет минимальное значение из списка или null при отсутствии элементов. Работает с базовыми типами. Функция min делает тоже самое, но выбрасывает исключение при отсутствии элементов (см. пример с max()).

maxByOrNull() возвращает первый элемент с наибольшим значением заданной функции или null, если элементов нет. Работает со всеми типами. Аналогичная функция minByOrNull() возвращает первый элемент с наименьшим значением.

Выберем самого старшего кота.


val cats = listOf(Cat("Барсик", 5, 4),
        Cat("Мурзик", 9, 2))
println(cats.maxByOrNull { it.age })
// println(cats.maxByOrNull (Cat::age)) // альтернативный вариант

Функция maxWithOrNull() позволяет использовать Comparator. Выберем кота с самым длинным именем.


val cats = mutableListOf<Cat>()
cats.add(Cat("Мурзик", 9, 5400))
cats.add(Cat("Рыжик", 5, 6500))
cats.add(Cat("Василий", 4, 5100))

val maxLengthName:Cat? = cats.maxWithOrNull(
        Comparator{cat1, cat2 ->
            cat1.name.length - cat2.name.length
        }
)
maxLengthName?.apply {
    println("${this.name}:${this.age}:${this.weight}")
}

Кота с самым коротким именем можно вычислить через похожую функцию minWithOrNull().

В Kotlin 1.4 появилась новая функция, вычисляющая наибольшее или наименьшее число.


val max = maxOf(3, 9, 6)
println(max)

val min = minOf(3, 9, 6)
println(min)

Она удобна при работе с классами для выборки одного поля. Вычислим самого молодого котика. Функция вернёт не котика, а его возраст.


val junior = cats.minOf { it.age }

Есть родственные функции minBy(), maxBy(), minWith(), maxWith().

sumBy() подсчитывает сумму всех элементов, после того, как они подверглись изменению.


// увеличиваем все элементы на 5, а потом складываем
println(listOf(1, 2, 3).sumBy { it + 5 }) // 21

// Считаем сумму длин строк
println(listOf("cat", "kitten").sumBy { it.length })

При работе с числами Double используйте функцию sumByDouble().

В Kotlin 1.4 появилась новая функция sumOf(), позволяющая суммировать элементы разных типов. Сложим возраст (Int) и вес (Double) всех котов.


data class Cat(
    val name: String,
    val age: Int,
    val weight: Double
)

val cats = listOf(
    Cat("Murzik", age = 10, weight = 1.4),
    Cat("Barsik", age = 5, weight = 3.1),
    Cat("Ryzik", age = 1, weight = 2.2))
	
// суммируем разные типы
val total = cats.sumOf { it.age * it.weight} // Double
println(total)
val count = cats.sumOf { it.age } // Int
println(count)

Также появились minOfWith() и maxOfWith(), работающие с компаратором.

drop() отсекает указанное число элементов. Также есть функции dropWhile, dropLastWhile.


println(listOf(1, 2, 3, 4, 5).drop(2)) // [3, 4, 5]
println(listOf(1, 2, 3, 4, 5).dropWhile({ it < 3}))  // [3, 4, 5]
println(listOf(1, 2, 3, 4, 5).dropLastWhile { it > 4 })  // [1, 2, 3, 4]

take оставит первые элементы списка. takeLast оставит последние элементы.


// оставим первые два элемента
println(listOf(12, 32, 34, 45, 45).take(2)) // [12, 32]

// оставим последние два элемента
println(listOf(12, 32, 34, 45, 45).takeLast(2)) // [45, 45]

takeWhile оставит первые элементы, которые соответствуют условию.


println(listOf(1, 2, 3, 4, 5).takeWhile{it < 3}) // [1, 2]

// вернёт первые три символа
val chars = mutableListOf('c', 'a', 't', 'z', 'b', 'c', 'd', 'a', 'e', 'e', 'f', 'a')
val taken = chars.takeWhile { it < 'u' }
taken.forEach{
    println("$it")
}

Соответственно, takeLastWhile() оставит последние элементы, соответствующие условию.


val chars = mutableListOf('c', 'a', 't', 'z', 'b', 'c', 'd', 'a', 'e', 'e', 'f', 'a')
val taken = chars.takeLastWhile { it < 'e' } // вернёт последний символ 'a'
taken.forEach{
    println("$it")
}

Обратите внимание на разницу между takeWhile() и filter() (см. ниже). Первая функция будет отбирать элементы, пока выполняется условие и прервётся, а вторая пройдётся по всему списку до конца.


val chars = mutableListOf('c', 'a', 't', 'z', 'b', 'c', 'd', 'a', 'e', 'e', 'f', 'a')
var taken = chars.takeWhile { it < 'u' } // вернёт первые три символа, которые меньше символа 'u'
taken.forEach{
    println("takeWhile: $it")
}

taken = chars.filter { it < 'u' } // все символы, кроме 'z'
taken.forEach{
    println("filter: $it")
}

takeIf() будет выбирать элементы, если выполняется условие (предикат).


// Не выбирать элементы, если список содержит Пушистика
val cats = listOf("Рыжик", "Мурзик", "Барсик", "Васька")
cats.takeIf {
    it.contains("Пушистик")
}.apply {
    this?.forEach{
        println("$it")
    }
}

Обратная ситуация - выбирать элементы, если не выполняется условие (предикат).


// Выбрать элементы, если список не содержит Пушистика
val cats = listOf("Рыжик", "Мурзик", "Барсик", "Васька")
cats.takeUnless {
    it.contains("Пушистик")
}.apply {
    this?.forEach{
        println("$it")
    }
}

filter выполняет обход коллекции и вернёт список, который соответствует предикату (лямбда-выражение вернёт true). Функция может удалять элементы из коллекции, но не может изменять их (используйте map).


// больше или равно 3
println(listOf(1, 2, 3, 4, 5).filter { it >= 3 }) // [3, 4, 5]

// чётные числа
println(listOf(1, 2, 3, 4, 5).filter { it % 2 == 0 }) // [2, 4]

val cats = listOf("Барсик", "Мурзик", "Васька", "Рыжик")
// слова, у которых второй символ "а"
println(cats.filter { it[1] == 'а' })
// [Барсик, Васька]

// Содержит "ик", сортируем по длине слова
val cats = listOf("Барсик", "Мурзик", "Пикассо", "Васька", "Рыжик")
val filtered = cats.filter { it.contains("ик") }.sortedBy { it.length }
println(filtered)

// Начинается на "П" и оканчивается на "к"
val cats = listOf("Барсик", "Мурзик", "Пикассо", "Васька", "Рыжик", "Пушок")
val filtered = cats.filter { it.startsWith('П') } .filter { it.endsWith('к') }
println(filtered) // Пушок

// Оставляем только толстых котов по весу
val cats = mutableListOf<Cat>()
cats.add(Cat("Murzik", 9, 5400))
cats.add(Cat("Barsik", 5, 6500))
cats.add(Cat("Vaska", 4, 5100))

val filteredCats = cats.filter { it.weight > 5200 }

filteredCats.forEach {
    println("Name: ${it.name}; Age: ${it.age}; Weight: ${it.weight}")
}

// толстые и молодые коты (срочно в фитнес-цетр!)
val filteredCats = cats.filter { it.age < 6 && it.weight > 5200 }

filterNot вернёт список, который не соответствует предикату.


// оставляем нечётные числа
println(listOf(1, 2, 3, 4, 5).filterNot { it % 2 == 0 }) // [1, 3, 5]

filterNotNull вернёт новый список, не содержащий null.


val cats = listOf("Мурзик", null, "Барсик", "Рыжик", null, "Васька", "Пушистик", null)
// Оставляем только котов
val filtered = cats.filterNotNull()
filtered.forEach {
    println("$it")
}

Расширенная версия filterNotNullTo() уберёт все элементы null и добавит оставшиеся элементы в новый список.


val cats = listOf("Мурзик", null, "Барсик", "Рыжик", null, "Васька", "Пушистик",
        null)

val allCats = mutableListOf("Мурка", "Милка")

cats.filterNotNullTo(allCats)

println(allCats.joinToString())

// Мурка, Милка, Мурзик, Барсик, Рыжик, Васька, Пушистик

contains() позволит убедиться, что нужный элемент присутствует в списке.


// содержится ли число 4 в списке
println(listOf(1, 2, 3, 4, 5).contains(4)) // true

// содержится ли в списке Барсик
val cats = listOf("Мурзик", "Барсик", "Рыжик")
println("Список содержит Барсика: ${cats.contains("Барсик")}")

Если нужно проверить сразу несколько элементов, то используйте containsAll(), передавая ему список желаемых элементов.


// содержится ли в списке Барсик и Мурзик
val cats = listOf("Мурзик", "Барсик", "Рыжик")
println("Барсик и Мурзик в списке: ${cats.containsAll(listOf("Барсик", "Мурзик"))}")

Получить элемент по индексу можно через elementAt(). Также доступны elementAtOrElse, elementAtOrNull.


println(listOf(1, 2, 3, 4, 5).elementAt(3)) // 4

first() вернёт первый элемент, соответствующий предикату. Если элемент не будет найден, то получим исключение NoSuchElementException. Похожая функция firstOrNull вернёт null, если элемент не будет найден.


println(listOf(1, 2, 3, 4, 5).first{it % 3 == 0}) // 3

В Kotlin 1.5 появились новые функции firstNotNullOf() и firstNotNullOfOrNull(). Функция firstNotNullOf() возвращает первое ненулевое значение заданного селектора или исключение, если элемент не будет найден и является комбинацией двух функций mapNotNull() и first(). Функция firstNotNullOfOrNull() возвращает первое ненулевое значение заданного селектора или null, если элемент не будет найден. Функция firstNotNullOfOrNull() - комбинация mapNotNull() и firstOrNull().


data class Cat(
    val name: String,
    val age: Int?,
    val weight: Int
)

val cats = listOf(
    Cat("Васька", null, 3),
    Cat("Рыжик", null, 4),
    Cat("Барсик", 11, 5),
    Cat("Мурзик", 4, 6),        
)

val firstOld = cats.firstNotNullOfOrNull{it.age}
println(firstOld) // 11

По такому же принципу работают last() и lastOrNull() для последнего элемента.

find похож на first. Возвращает первый найденный элемент, если их несколько или null, если ни один не удовлетворяет предикату.


println(cats.find{it.weight < 4})

indexOf() вернёт индекс элемента. Функции indexOfFirst(), indexOfLast, lastIndexOf() работают с предикатами.


println(listOf(1, 2, 3, 4, 5).indexOf(4)) // 3

Функция single() вернёт один уникальный элемент из списка. Если элементов, соответствующих условию, будет несколько будет исключение. singleOrNull вместо исключения вернёт null.


println(listOf(1, 6, 3, 4, 5).singleOrNull { it % 3 == 0 }) // null

Развернуть элементы списка можно через reverse() и reversed().


val list = mutableListOf(5, 9, 1)
list.reverse() // у изменяемого списка
println(list) // [ 1, 9, 5]

val list = listOf(1, 2, 3)
println(list.reversed()) //[3, 2, 1] // новый список, оригинальный остаётся без изменений

Сортировка: sort, sorted, sortedDescending, sortBy, sortByDescending, sortWith

Сортировка оригинального списка происходит через sort().


// используем изменяемый список
val list = mutableListOf(5, 9, 1)
list.sort()
println(list)

Отсортировать список можно через sorted(). Возвращается новый список, а оригинальный список остаётся без изменений.


val list = listOf(5, 9, 1)
println(list.sorted()) // [1, 5, 9]

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


val list = listOf(5, 9, 1)
println(list.sortedDescending())
// [9, 5, 1]

Сортировка по условию. Отсортируем по именам котов при помощи sortBy().


// класс Cat описан выше

val cats = mutableListOf<Cat>()
cats.add(Cat("Murzik", 9, 5400))
cats.add(Cat("Barsik", 5, 6500))
cats.add(Cat("Vaska", 4, 5100))

cats.sortBy { it.name }
cats.forEach {
    println("Name: ${it.name}; Age: ${it.age}; Weight: ${it.weight}")
}

Аналогично в обратном порядке через sortByDescending().

Если элементы содержат null, то можно сортировать при помощи sortWith() в связке с nullFirst().


// сначала null, потом остальные элементы по порядку
val cats = mutableListOf("cat", "murzik", null, "barsik", null, "kitten")
cats.sortWith(
        nullsFirst(compareBy{it})
)
cats.forEach{
    println("$it")
}

Обратная задача решается при помощи nullLast.


val cats = mutableListOf("cat", "murzik", null, "barsik", null, "kitten")
cats.sortWith(
        nullsLast(compareByDescending{it})
)
cats.forEach{
    println("$it")
}

Можно сортировать, сравнивая несколько полей класса. Порядок важен. Будем сортировать по имени и по возрасту (имена и возраст могут совпадать в списке).


val cats = mutableListOf<Cat>()
cats.add(Cat("Мурзик", 4, 5400))
cats.add(Cat("Рыжик", 5, 6500))
cats.add(Cat("Василий", 4, 5100))
cats.add(Cat("Мурзик", 6, 5400))

cats.sortWith(
        compareBy(
                {it.name}, {it.age}
        )
)

cats.forEach {
    println("${it.name}: ${it.age}, ${it.weight}")
}

Сортируем по длине имён в порядке возрастания, используя Comparator.


val cats = mutableListOf<Cat>()
cats.add(Cat("Мурзик", 4, 5400))
cats.add(Cat("Рыжик", 5, 6500))
cats.add(Cat("Мордотресь", 4, 5100))

cats.sortWith(
        Comparator { cat1, cat2 ->
            cat1.name.length - cat2.name.length
        }
)

cats.forEach {
    println("${it.name}: ${it.age}, ${it.weight}")
}

Если в порядке убывания, то поменяем местами аргументы.


cat2.name.length - cat1.name.length

Можно сложить два списка. К элементам первого списка будут добавлены элементы второго списка.


val list1 = listOf(1, 2, 3)
val bigList = list1 + listOf(4, 5)
println(bigList)
// [1, 2, 3, 4, 5]

Добавить ещё один элемент можно через plus(). Оригинальный список останется без изменений и будет создан новый список.


val list = listOf(1, 2, 3, 4, 5)
println(list.plus(6)) // новый список [1, 2, 3, 4, 5, 6]
println(list) // старый список [1, 2, 3, 4, 5]

Функция minus() отнимает указанный элемент.


val list = listOf(1, 2, 3, 4, 5)
println(list.minus(3)) // [1, 2, 4, 5]

Функция average() подсчитывает среднее значение всех элементов списка.


val list = listOf(1, 2, 3, 4, 5)
println(list.average()) // 3.0

Функция slice возвращает список по указанным индексам.


println(listOf(1, 2, 3, 4, 5).slice(listOf(1, 3, 4)))  // [2, 4, 5]

Функция shuffled() создаёт новый список с перемешанными элементами от старого списка.


val items = listOf(1, 2, 3)
val newList = items.shuffled()

println("shuffled: $newList") // shuffled: [2, 1, 3] один из вариантов

Функция shuffle() (Kotlin 1.2) перемешивает элементы изменяемого списка в случайном порядке.


val items = (1..5).toMutableList()

items.shuffle()
println("Shuffled items: $items")
// [2, 3, 1, 4, 5] один из вариантов

Разбить список на отдельные элементы с определённым условием можно через функцию partition. При значении true элементы попадают в одну группу, а остальные в другую. Возвращается объект Pair, содержащий два списка.


// по признаку чётности
val listA = listOf(1, 2, 3, 4, 5, 6)
val pair = listA.partition {
    it % 2 == 0
}
println(pair)
// ([2, 4, 6], [1, 3, 5])

Разделим котов на молодых и пожилых.


val cats = mutableListOf<Cat>()
cats.add(Cat("Мурзик", 4, 5400))
cats.add(Cat("Рыжик", 15, 6500))
cats.add(Cat("Барсик", 14, 5100))
cats.add(Cat("Васька", 7, 5400))

val (youngs, olds) = cats.partition { it.age < 10 }

println(youngs.joinToString())
println(olds.joinToString())

groupBy группирует значения по некоторому критерию. Например, мы хотим разбить список котов по возрасту.


val cats = listOf(Cat("Barsik", 5),
        Cat("Murzik", 9), Cat("Ryzhik", 5))
println(cats.groupBy { it.age })

// Результат
{5=[Cat(name=Barsik, age=5), Cat(name=Ryzhik, age=5)],
 9=[Cat(name=Murzik, age=9)]}

На выходе получается словарь с ключами (Map<Int, List<Cat>>), которые определяют признак группировки (в нашем случае это возраст). Мы получили одну группу котов с возрастом 5 лет и вторую группу с возрастом 9 лет. Вы можете изменить словарь при помощи mapKeys и mapValues.

Список может содержать дубликаты. Но если вы хотите избавиться от них, то можно преобразовать список во множество, а затем снова в список. Существует готовая функция distinct(), которая сделает это за вас.


val cats = listOf("Рыжик", "Мурзик", "Барсик", "Рыжик")
        .distinct()
println(cats)

Можно задать более точный критерий - например, ищем дубликаты по имени или по возрасту через distinctBy().


val cats = mutableListOf<Cat>()
cats.add(Cat("Мурзик", 4, 5400))
cats.add(Cat("Рыжик", 5, 6500))
cats.add(Cat("Барсик", 4, 5100))
cats.add(Cat("Рыжик", 7, 5400))

val distinctList: List<Cat> = cats.distinctBy { it.name } // либо it.age
println(distinctList.joinToString())

Можно даже создать сразу несколько условий для поиска дубликатов. Например, поищем дубликаты и по имени и по возрасту. Пусть у нас будет целая орава котов с одинаковыми именами. Некоторые из них могут быть одного возраста. Вес тоже может совпадать, но пока он нас не интересует.


val cats = mutableListOf<Cat>()
cats.add(Cat("Рыжик", 4, 5400))
cats.add(Cat("Рыжик", 5, 6500))
cats.add(Cat("Рыжик", 6, 5100))
cats.add(Cat("Рыжик", 5, 5400))

val distinctList: List<Cat> = cats.distinctBy { it.name to it.age }
println(distinctList.joinToString())

emptyList()

Пустой неизменяемый список можно создать через emptyList(), который вернёт тип List.


val emptyList: List<String> = emptyList<String>()

listOfNotNull()

Ещё один вид неизменяемых списков - listOfNotNull(). Вы можете поместить в список null, но они будут отсечены.


val cats = listOf("Мурзик", null, "Барсик", "Рыжик", null, "Васька", "Пушистик", null)
val notNullsCats = listOfNotNull("Мурзик", null, "Барсик", "Рыжик", null, "Васька", "Пушистик", null)

println(cats.joinToString()) // выводит все элементы, включая null
println(notNullsCats.joinToString()) // все элементы без null

// Мурзик, null, Барсик, Рыжик, null, Васька, Пушистик, null
// Мурзик, Барсик, Рыжик, Васька, Пушистик

arrayListOf()

Изменяемый список создаётся через arrayListOf(), который возвращает ArrayList.


//val stringList: ArrayList<String> = arrayListOf<String>("Hello", "Kitty")
val stringList = arrayListOf("Hello", "Kitty")
println(stringList::class.java) // class java.util.ArrayList

Сделаем обход коллекции с использованием индекса.


val list = arrayListOf("3", "8", "4", "1", "9")
for ((index, element) in list.withIndex()){
    println("$index: $element")
}

// Результат
0: 3
1: 8
2: 4
3: 1
4: 9

mutableListOf()

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


var mutableNames = names.toMutableList() // превращаем в изменяемый список
mutableNames.add("Рыжик") // добавляем новое имя в конец списка
println(mutableNames::class.java)
println(mutableNames[3])

А можно сразу создать изменяемый список нужного типа через mutableListOf<T>(). Также есть перегруженная версия без указания нужного типа - mutableListOf().


val mutableListNames: MutableList<String> =
        mutableListOf<String>("Барсик", "Мурзик", "Васька")
mutableListNames.add("Рыжик") // добавляем в конец списка
mutableListNames.removeAt(1) // удаляем второй элемент
mutableListNames[0] = "Пушок" // заменяем первый элемент через присваивание
// mutableListNames.set(0, "Пушок") // другой вариант
mutableListNames.add(1, "Begemoth") // вставляем во вторую позицию

// изменяемый список из разных типов
val mutableListMixed = mutableListOf("Кот", "Собака", 5, 5.27, 'F')

При изменении списков индексы пересчитываются.

Удалить элемент из изменяемого списка можно через removeIf(), указав лямбда-выражение в качестве условия.


val cats = mutableListOf("Мурзик", "Барсик", "Рыжик")
cats.removeIf{it.contains("у")}
println(cats)

// выводится: [Барсик, Рыжик]

Удалить все элементы, которые подчиняются условию, можно также через removeAll():


val cats = mutableListOf("Мурзик", "Барсик", "Рыжик", "Васька")
// Удаляем элементы, которые начинаются на 'Б'
cats.removeAll { it.startsWith("Б") }

cats.forEachIndexed { index, name ->
    println("${index + 1}. $name")
}

//
1. Мурзик
2. Рыжик
3. Васька

Если надо не удалить, а оставить элементы по определённому условию, то используйте функцию retainAll().


val cats = mutableListOf("Мурзик", "Барсик", "Рыжик", "Васька")
// Оставляем элементы, которые заканчиваются на 'ик'
cats.retainAll { it.endsWith("ик") }

cats.forEachIndexed { index, name ->
    println("${index + 1}. $name")
}

Операции с двумя списками

С помощью addAll() можно добавить в список элементы другого списка.


var listA = mutableListOf("a", "a", "b")
var listB = mutableListOf("a", "c", "d")
listB.addAll(listA)
println(listB) // [a, c, d, a, a, b]

Другой способ - функция plusAssign():


var listA = mutableListOf("a", "a", "b")
var listB = mutableListOf("a", "c", "d")
listB.plusAssign(listA)
println(listB) // [a, c, d, a, a, b]

Вычитать элементы одного списка при помощи элементов другого списка можно через minusAssign().


var listA = mutableListOf("a", "a", "b", "u")
var listB = mutableListOf("a", "c", "d", "e", "u")
listB.minusAssign(listA)
println(listB) // [c, d, e]

Ещё один способ объединения двух списков с сохранением только уникальных элементов через union(). Обратите внимание на порядок сохранения - сначала берутся элементы основного списка, а затем добавляются элементы добавляемого списка.


val listA = mutableListOf("a", "e", "b")
val listB = mutableListOf("a", "c", "d", "e", "f")
val listC = listB.union(listA)
println(listC)

Также можно добавлять элементы в список через операторы + и -. Создадим третий список, используя два списка и отдельные элементы.


val firstList = listOf(1, 2, 3)
val secondList = listOf(6, 7, 8, 9, 10)
val thirdList = firstList + 4 + 5 + secondList
println(thirdList); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Попробуем вычитать элементы из списка.


val firstList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
val secondList = firstList - 4 - 7
println(secondList) // [1, 2, 3, 5, 6, 8, 9]

Допустим и такой вариант.


val secondList = firstList - 4 - listOf(5, 6, 7, 8, 9) // [1, 2, 3]

Интересный случай с использованием += и -=. В этом случае следует использовать var, так как мы изменяем список.


var firstList = listOf(1, 2, 3)
firstList += listOf(4, 5)
println(firstList) // [1, 2, 3, 4, 5]

replaceAll (Kotlin 1.2)

Заменяет каждый элемент списка новым значением в результате заданного выражения. Доступно для API 24 и выше


val items = (1..5).toMutableList()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    items.replaceAll { it * 2 }
}
println("Значения удвоены: $items") // [2, 4, 6, 8, 10]

fill (Kotlin 1.2)

Заменяет все элементы списка заданным значением.


val items = (1..5).toMutableList()

println("Было: $items")
items.fill(5)
println("Стало: $items")

//Было: [1, 2, 3, 4, 5]
//Стало: [5, 5, 5, 5, 5]

Все коллекция являются стандартными и полностью совместимыми с Java, Kotlin не использует собственных коллекций, но тем не менее предлагает удобные методы из коробки. Выше были показаны примеры для списков. Можно применять методы для других коллекций.

Например, найдём последний элемент из коллекции.


val set = setOf("Васька", "Мурзик", "Барсик")
println(set.last())

joinToString()

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

Функция использует параметры по умолчанию, поэтому часть параметров можно опускать.


val numbers = listOf(1, 2, 3, 4, 5, 6)
println(numbers.joinToString()) // 1, 2, 3, 4, 5, 6
println(numbers.joinToString(prefix = "[", postfix = "]")) // [1, 2, 3, 4, 5, 6]
println(numbers.joinToString(prefix = "<", postfix = ">", separator = "•")) // <1•2•3•4•5•6>

val chars = charArrayOf('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q')
println(chars.joinToString(limit = 5, truncated = "...!") { it.toUpperCase().toString() }) // A, B, C, D, E, ...!

Создать список из случайных чисел

В Kotlin можно создать функцию для генерации списка из случайных чисел очень лаконичным способом.


private val random = Random()
private fun buildRandom() = List(25) { random.nextInt() }

// вызываем функцию
println(buildRandom())

ArrayDeque (Kotlin 1.3.70)

Новый класс.


val deque = ArrayDeque(listOf(1, 2, 3))

deque.addFirst(0)
deque.addLast(4)
println(deque) // [0, 1, 2, 3, 4]

println(deque.first()) // 0
println(deque.last()) // 4

deque.removeFirst()
deque.removeLast()
println(deque) // [1, 2, 3]

Упражнения для коллекций

Ответы на упражнения

Имеется список, некоторые элементы которого повторяются несколько раз. Найти самый повторяемый элемент.

Реклама