Фильтрация коллекций
Фильтрация — одна из самых популярных задач при работе с коллекцией. В 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
}