Освой Kotlin играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Свойство indices
val vs var
Конструктор Array()
Класс Arrays
Перевернуть массив: reversedArray()
Перевернуть массив: reverse()
Сортировка элементов массива
Содержится ли элемент в массиве
Подсчитать сумму чисел в массиве
Найти среднее значение чисел в массиве
Найти наибольшее и наименьшее число в массиве
Функция intersect(): найти общие элементы двух массивов
Выбрать случайную строку из массива
shuffle(): Перемешать элементы (Kotlin 1.40)
onEach(): Операция с каждым элементом массива по очереди (Kotlin 1.40)
Удалить дубликаты
Двумерные массивы
Рассмотрим работу с массивами в Kotlin, которые являются классами.
Массив можно создать двумя способами - через конструктор Array() или через методы arrayOf(), arrayOfNulls(), emptyArray().
Создадим массив и получим значение третьего элемента.
val myArray = arrayOf(1, 2, 3, 4, 5)
println(myArray[2])
Узнать длину массива можно при помощи свойства size.
println(myArray.size) // 5
А что случится, если мы добавим в массив строки?
val myArray = arrayOf(1, 2, 3, 4, 5, "зайчик", "вышел", "погулять")
println(myArray[5])
Ничего страшного, у нас получился массив смешанного типа. Всё работает, ничего не ломается.
Если мы хотим строгого поведения и не хотим смешивать разные типы, то используем обобщения.
val myArray = arrayOf<Int>(1, 2, 3, 4, 5) // только числа Integer
Существует также синонимы метода, когда уже в имени содержится подсказка: intArrayOf(), harArrayOf(), booleanArrayOf(), longArrayOf(), shortArrayOf(), byteArrayOf().
Перепишем пример.
val myArray = intArrayOf(1, 2, 3, 4, 5)
Пройтись по элементам массива и узнать значение индекса можно с помощью метода withIndex():
val numbersArray = intArrayOf(1, 2, 3, 4, 5)
for ((index, value) in numbersArray.withIndex()) {
println("Значение индекса $index равно $value")
}
У массива есть свойство indices и мы можем переписать пример по другому.
val numbers = intArrayOf(1, 2, 3, 4, 5)
for (index in numbers.indices) {
println("Значение индекса $index равно ${numbers[index]}")
}
Свойство возвращает интервал Range, который содержит все индексы массива. Это позволяет не выйти за пределы массива и избежать ошибки ArrayIndexOutOfBoundsException.
Но у свойства есть очень интересная особенность. Взгляните на код:
val numbers = intArrayOf(1, 2, 3, 4, 5)
for(index in numbers.indices - 2) {
println(numbers[index])
}
// 1 2 4 5
Из интервала индексов массива мы убрали третий элемент (отсчёт от 0). И теперь при выводе элементов массива мы не увидим числа 3.
Можно сложить два массива.
val numbers = intArrayOf(1, 2, 3)
val numbers3 = intArrayOf(4, 5, 6)
val foo2 = numbers3 + numbers
println(foo2[5]) // 3
Для создания массива с заполненными значениями null можно использовать отдельную функцию arrayOfNulls().
Создадим массив с тремя элементами.
val array = arrayOfNulls(3) // [null, null, null]
// равносильно
// arrayOf(null, null, null)
Присвоим значения пустым элементам.
var arr2 = arrayOfNulls<String>(2)
arr2.set(0, "1")
arr2.set(1, "2")
// или
arr2[0] = "1"
arr2[1] = "2"
// получить значения
println(arr2[0]) // или arr2.get(0)
println(arr2[1])
Создадим пустой массив и заполним его данными.
var arr = emptyArray<String>()
arr += "1"
arr += "2"
arr += "3"
arr += "4"
arr += "5"
Нужно уяснить разницу между var и val при работе с массивами.
// Создали новый массив
var myArray = arrayOf(1, 2, 3)
// Это совершенно новый массив
myArray = arrayOf(4, 5)
Фактически мы уничтожили первый массив и создали вместо него второй массив.
Если мы попытаем написать такой же код с использованием val, то компилятор запретит такое действие.
// Создали новый массив
val myArray = arrayOf(1, 2, 3)
// Нельзя. Компилятор не пропустит
myArray = arrayOf(4, 5)
Но при этом вы можете менять значения элементов массива, созданного через val.
val myArray = arrayOf(1, 2)
myArray[0] = 3 // меняем первый элемент массива
myArray[1] = 4 // меняем второй элемент массива
При использовании конструктора нужно указать размер массива в первом параметре и лямбда-выражение во втором.
val myArray = Array(5, { i -> i * 2 })
println(myArray[3])
Мы задали пять элементов и каждый элемент в цикле умножаем на 2. В итоге получим массив чисел 0, 2, 4, 6, 8.
Создадим массив строк от "A" до "Z"
val letters = Array<String>(26) { i -> ('A' + i).toString() }
println(letters.joinToString(""))
Лямбда-выражение принимает индекс элемента массива и возвращает значение, которое будет помещено в массив с этим индексом. Значение вычисляется путём сложения индекса с кодом символа и преобразованием результата в строку.
Можно опустить тип массива и написать Array(26), компилятор самостоятельно определит нужный тип.
Есть отдельные классы для каждого примитивного типа - IntArray, ByteArray, CharArray и т.д.
val zeros = IntArray(3) // первый способ
val zeros = intArrayOf(0, 0, 0) // второй способ при помощи фабричного метода
println(zeros.joinToString())
Можно использовать лямбда-выражение.
val intArray = IntArray(4){i -> i + i}
println(intArray.joinToString())
Для вывода значений массива используйте класс Arrays с методом toString(), который вернёт результат в удобном и читаемом виде. Сейчас в Kotlin появилась функция contentToString(), которая является предпочтительным вариантом.
println(Arrays.toString(arr)) // старый способ
println(arr.contentToString()) // рекомендуемый способ
Обычный перебор через for.
val arr = arrayOf(1, 2, 3, 4, 5)
for (i in arr) {
println("Значение элемента равно $i")
}
Можно одной строкой через forEach.
arr.forEach { i -> println("Значение элемента равно $i") }
Если нужна информация не только о значении элемента, но и его индексе, то используем forEachIndexed.
arr.forEachIndexed { index, element ->
println("$index : $element")
}
// Результат
0 : 1
1 : 2
2 : 3
3 : 4
4 : 5
Для операции создадим дополнительную переменную для нового массива с перевёрнутыми значениями. Оригинал останется без изменений.
val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
var reversedArray = numbers.reversedArray()
println(Arrays.toString(reversedArray))
Если оригинал массива нам точно не понадобится, то мы можем перевернуть его без создания нового массива.
val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
numbers.reverse()
println(Arrays.toString(numbers))
В Kotlin очень просто сортировать элементы.
Вызываем метод sort(). Мы меняем существующий массив, а не создаём новый.
val numbers: IntArray = intArrayOf(7, 5, 8, 4, 9, 6, 1, 3, 2)
numbers.sort()
// println(Arrays.toString(numbers)) // старый способ
println("Sorted array: ${numbers.contentToString()}")
Сортировать можно не весь массив, а только определённый диапазон. Указываем начало и размер диапазона. Допустим, нам нужно отсортировать только первые три элемента из предыдущего примера.
numbers.sort(0, 3)
// 5, 7, 8, 4, 9, 6, 1, 3, 2
Сортировка в обратном порядке от наибольшего значения к наименьшему.
numbers.sortDescending()
Если нужно сохранить исходный массив, то вызываем другие функции, которые создадут новый массив.
val numbers: IntArray = intArrayOf(7, 5, 8, 4, 9, 6, 1, 3, 2)
val sortedNumbers: IntArray = numbers.sortedArray() // новый сортированный массив
val descendingSortedNumber: IntArray = numbers.sortedArrayDescending() // новый сортированный массив в обратном порядке
println("Original array ${numbers.contentToString()}:Sorted array ${sortedNumbers
.contentToString()}")
// Original array [7, 5, 8, 4, 9, 6, 1, 3, 2]:Sorted array [1, 2, 3, 4, 5, 6, 7, 8, 9]
Для сортировки объектов указываем компаратор и условие сравнения. Например, мы хотим сравнить котов по их возрастам.
val cats = arrayOf(Cat("Барсик", 8), Cat("Мурзик", 4), Cat("Васька", 9))
// массив до сортировки
cats.forEach { println(it) }
// сортируем по возрасту
cats.sortWith(Comparator { c1: Cat, c2: Cat -> c1.age - c2.age })
cats.forEach { println(it) }
data class Cat(val name: String, val age: Int)
Вместо компаратора можно использовать функцию sortBy() с указанием условия. Сравним теперь котов не по возрасту, а по их именам.
val cats = arrayOf(Cat("Барсик", 8), Cat("Мурзик", 4), Cat("Васька", 9))
cats.forEach { println(it) }
cats.sortBy { cat -> cat.name }
cats.forEach { println(it) }
data class Cat(val name: String, val age: Int)
Если содержится, то возвращает true.
val array = arrayOf(1, 2, 3, 4, 5)
val isContains = array.contains(9)
println(isContains) // false
Используем функцию average(). Возвращается Double.
val array = arrayOf(1, 3, 5)
println(array.average()) // 3.0
val array = arrayOf(1, 2, 3, 4, 5)
println(array.sum()) // 15
В цикле сравниваем каждое число с эталоном, которые вначале принимает значение первого элемента. Если следующее число массива больше эталона, то берём его значение. В итоге после перебора получим наибольшее число в массиве.
val numbers: IntArray = intArrayOf(4, 9, 3, 2, 6)
var largestElement = numbers[0]
for (number in numbers){
if(largestElement < number)
largestElement = number
}
println("Наибольшее число в массиве: $largestElement")
Но можно не писать свой код, а вызвать готовые функции min() и max().
println(numbers.min())
println(numbers.max())
Есть два массива с числами. Нужно сравнить их и найти у них общие числа. Поможет нам функция intersect()
val firstArray = arrayOf(1, 2, 3, 4, 5)
val secondArray = arrayOf(3, 5, 6, 7, 8)
val intersectedArray = firstArray.intersect(secondArray.toList()).toIntArray()
println(Arrays.toString(intersectedArray))
//[3, 5]
Имеется массив строк. Сначала вычисляем размер массива. Затем генерируем случайное число в диапазоне от 0 до (почти) 1, которое умножаем на количество элементов в массиве. После этого результат преобразуется в целое число вызовом toInt(). Получается выражение типа 0.811948208873101 * 5 = 4. В Kotlin есть свой класс Random, поэтому случайное число можно получить другим способом.
val cats = arrayOf("Барсик", "Мурзик", "Васька", "Рыжик", "Персик")
val arraySize = cats.size
// Java-style
val rand = (Math.random() * arraySize).toInt()
val name = "${cats[rand]}}"
println(name)
// Kotlin-style
val rand = Random.nextInt(arraySize)
val name = "${cats[rand]}"
println(name)
По этому принципу можно создать игру "Камень, Ножницы, Бумага".
private fun getChoice(optionsParam: Array<String>) =
optionsParam[Random.nextInt(optionsParam.size)]
val options = arrayOf("Камень", "Ножницы", "Бумага")
val choice = getChoice(options)
println(choice)
Перемешать элементы массива в случайном порядке можно при помощи метода shuffle().
val numbers = arrayOf(1, 2, 3, 4, 5)
numbers.shuffle()
println(numbers.contentToString())
В коллекциях мы можем пройтись по всем элементам и что-то с каждым сделать. Теперь такое возможно и с элементами массива. Пройдёмся по всем числам массива, удвоим каждое число и конвертируем в строку.
var str = ""
val numbers = arrayOf(1, 2, 3, 4, 5)
numbers.onEach {str += it * 2}
println(str)
Удалить дубликаты можно несколькими способами. Например, через toSet()
val myArray = arrayOf(1, 1, 2, 3, 4, 5, 5, 4, 3, 2)
println(myArray.toSet().joinToString())
Мы получим множество, которое не допускает дубликатов. Порядок элементов сохраняется. Обратно в массив можно преобразовать через toIntArray() или схожие функции.
Аналогично можно использовать toHashSet(), получив HashSet, который тоже не допускает дубликатов, но не гарантирует очерёдность элементов.
Другой вариант - toMutableSet(). Порядок сохранится.
Самый простой вариант - вызвать функцию distinct(), который вернёт новый массив без дубликатов.
val myArray = arrayOf(1, 1, 2, 3, 4, 5, 5, 4, 3, 2)
println(myArray.joinToString())
val newArray = myArray.distinct()
println(newArray.joinToString())
Часто одного массива недостаточно. В некоторых случаях удобно использовать двумерные массивы. Визуально их легко представить в виде сетки. Типичный пример - зал в кинотеатре. Чтобы найти нужно место в большом зале, нам нужно знать ряд и место.
Двумерный массив - это массив, который содержит другие массивы. Создадим двумерный массив 5х5 и заполним его нулями.
// Создаём двумерный массив
var cinema = arrayOf<Array<Int>>()
// заполняем нулями
for (i in 0..4) {
var array = arrayOf<Int>()
for (j in 0..4) {
array += 0
}
cinema += array
}
// выводим данные массива
for (array in cinema) {
for (value in array) {
print("$value ")
}
println()
}
// Результат
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
Сейчас в кинотеатре пусто. Первый зритель берёт билет в центре зала.
// центр зала
cinema[2][2] = 1
// три места во втором ряду
for (i in 1..3) {
cinema[3][i] = 1
}
// весь первый ряд
for (i in 0..4) {
cinema[4][i] = 1
}
// выводим данные массива
for (array in cinema) {
for (value in array) {
print("$value ")
}
println()
}
// Результат
0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 1 1 1 0
1 1 1 1 1
По такому же принципу строится трёхмерный массив. На этот раз его можно представить не в виде сетки, а в виде куба. В этом случае сетки идут как бы друг за другом, образуя слои.
var rubikCube = arrayOf<Array<Array<Int>>>()
for (i in 0..2) {
var piece = arrayOf<Array<Int>>()
for (j in 0..2) {
var array = arrayOf<Int>()
for (k in 0..2) {
array += 0
}
piece += array
}
rubikCube += piece
}
// второй слой, третий ряд, первое место
rubikCube[1][2][0] = 1
println(Arrays.deepToString(rubikCube))
// Результат
0, 0, 0 | 0, 0, 0 | 0, 0, 0
0, 0, 0 | 0, 0, 0 | 0, 0, 0
0, 0, 0 | 1, 0, 0 | 0, 0, 0