Освой 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().

arrayOf()

Создадим массив и получим значение третьего элемента.


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

У массива есть свойство 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

arrayOfNulls()

Для создания массива с заполненными значениями 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])

emptyArray()

Создадим пустой массив и заполним его данными.


var arr = emptyArray<String>()
arr += "1"
arr += "2"
arr += "3"
arr += "4"
arr += "5"

val vs var

Нужно уяснить разницу между 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 // меняем второй элемент массива

Конструктор Array()

При использовании конструктора нужно указать размер массива в первом параметре и лямбда-выражение во втором.


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

Для вывода значений массива используйте класс 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

Перевернуть массив: reversedArray()

Для операции создадим дополнительную переменную для нового массива с перевёрнутыми значениями. Оригинал останется без изменений.


val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
var reversedArray = numbers.reversedArray()

println(Arrays.toString(reversedArray))

Перевернуть массив: reverse()

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


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(): найти общие элементы двух массивов

Есть два массива с числами. Нужно сравнить их и найти у них общие числа. Поможет нам функция 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(): Перемешать элементы (Kotlin 1.40)

Перемешать элементы массива в случайном порядке можно при помощи нового метода shuffle().


val numbers = arrayOf(1, 2, 3, 4, 5)
numbers.shuffle()
println(numbers.contentToString())

onEach(): Операция с каждым элементом массива по очереди (Kotlin 1.40)

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


var str = ""
val numbers = arrayOf(1, 2, 3, 4, 5)
numbers.onEach {str += it * 2}
println(str)

Двумерные массивы

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

Двумерный массив - это массив, который содержит другие массивы. Создадим двумерный массив 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
Реклама