Фильтрация коллекций
Фильтрация — одна из самых популярных задач при работе с коллекцией. В Kotlin условия фильтра задаются с помощью предикатов — лямбда-функций, которые принимают элемент коллекции, а возвращают логическое значение (boolean): true означает, что элемент соответствует предикату, false - не соответствует.
В стандартной библиотеке есть группа функций-расширений, с помощью которых можно фильтровать коллекции за один вызов такой функции. Они не изменяют исходную коллекцию, поэтому их можно использовать и для изменяемых, и для неизменяемых коллекций. Чтобы использовать результат фильтрации, вы должны либо присвоить его переменной, либо использовать его в цепочке вызовов.
Фильтр с предикатом
Основная функция для фильтра коллекций - filter().
Если использовать filter() с предикатом, то будут возвращены те элементы, которые ему соответствуют.
Для List и Set функция возвращает результат типа List, для Map возвращается Map.
fun main() {
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) // [three, four]
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filteredMap) // {key11=11}
}
В функции filter() предикаты могут проверять только значения элементов. Если при фильтрации вы хотите использовать индексы элементов, то вам нужна функция filterIndexed(). Она принимает предикат с двумя аргументами: индекс и значение элемента.
Если вы хотите отфильтровать элементы, которые не соответствуют заданному условию, то используйте функцию filterNot(). Она возвращает те элементы, для которых предикат вернул значение false.
fun main() {
val numbers = listOf("one", "two", "three", "four")
val filteredIdx = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5) }
val filteredNot = numbers.filterNot { it.length <= 3 }
println(filteredIdx) // [two, four]
println(filteredNot) // [three, four]
}
Также существуют функции, которые фильтруют только элементы заданного типа:
filterIsInstance()возвращает элементы заданного типа. При вызове функцииfilterIsInstance<T>()для списка типаList<Any>вернётся список типаList<T>, что позволяет вызывать специфичные для элементовTфункции.
fun main() {
val numbers = listOf(null, 1, "two", 3.0, "four")
println("All String elements in upper case:")
numbers.filterIsInstance<String>().forEach {
println(it.uppercase())
}
}
// В логе будет:
// All String elements in upper case:
// TWO
// FOUR
filterNotNull()возвращает значения неравныеnull. При вызове функции для спискаList<T?>вернётся список типаList<T: Any>, что позволяет обращаться к элементам как к ненулевым объектам.
fun main() {
val numbers = listOf(null, "one", "two", null)
numbers.filterNotNull().forEach {
println(it.length) // length is unavailable for nullable Strings
}
}
// В логе будет:
// 3
// 3
Фильтр с разделением
Другая функция для фильтрации - partition() - фильтрует коллекцию по предикату и сохраняет элементы, которые ему не соответствуют, в отдельном списке. Возвращает объект типа Pair<List, List>, где первый список содержит элементы, соответствующие предикату, а второй — все остальные элементы из исходной коллекции.
fun main() {
val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }
println(match) // [three, four]
println(rest) // [one, two]
}
Проверочные предикаты
Ну и наконец, есть функции, которые проверяют каждый элемент на соответствие предикату:
* any() - возвращает true, если хотя бы один элемент соответствует предикату.
* none() - возвращает true, если ни один элемент не соответствует предикату.
* all() - возвращает true, если все элементы соответствуют предикату. Обратите внимание, что если вызвать all() для пустой коллекции с любым валидным предикатом, то функция вернёт true. Такое поведение известно как бессмысленная истина (ориг. vacuous truth).
fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.any { it.endsWith("e") }) // true
println(numbers.none { it.endsWith("a") }) // true
println(numbers.all { it.endsWith("e") }) // false
println(emptyList<Int>().all { it > 5 }) // true - бессмысленная истина
}
any() и none() могут использоваться без предиката. В этом случае они просто проверяют пуста ли коллекция.
any() возвращает true, если в коллекции есть элементы, а none() наоборот, возвращает true, если коллекция пуста.
fun main() {
val numbers = listOf("one", "two", "three", "four")
val empty = emptyList<String>()
println(numbers.any()) // true
println(empty.any()) // false
println(numbers.none()) // false
println(empty.none()) // true
}