Можно ли использовать extension функцию для подсчета суммы по какому-то предикату в коллекции ключ-значение в Kotlin?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
Да, безусловно можно и очень удобно использовать extension-функции в Kotlin для подсчета суммы по предикату в коллекциях типа "ключ-значение". Это классический пример применения Kotlin DSL (Domain Specific Language) подходов для создания читаемого и переиспользуемого кода. Рассмотрим несколько практических вариантов.
Основные подходы с extension-функциями
1. Общий extension для Map
Создадим универсальную extension-функцию для Map<K, V>, где значение поддерживает арифметические операции:
fun <K, V : Number> Map<K, V>.sumByPredicate(predicate: (Map.Entry<K, V>) -> Boolean): Double {
return this.entries
.filter(predicate)
.sumOf { it.value.toDouble() }
}
// Использование
val map = mapOf("a" to 10, "b" to 20, "c" to 30, "d" to 5)
// Сумма значений, где ключ содержит букву 'a' или значение > 15
val result1 = map.sumByPredicate { it.key.contains("a") || it.value > 15 }
println(result1) // 60.0 (10 + 20 + 30)
// Сумма значений с четными ключами (если ключи - числа)
val map2 = mapOf(1 to 100, 2 to 200, 3 to 300)
val result2 = map2.sumByPredicate { it.key % 2 == 0 }
println(result2) // 200.0
2. Более специфичная версия для определенных типов
Если работаем преимущественно с определенными типами, можно создать более типобезопасные extensions:
fun Map<String, Int>.sumWhereKeyMatches(regex: Regex): Int {
return this.filter { regex.matches(it.key) }
.values.sum()
}
fun Map<String, Double>.sumWhereValueGreaterThan(threshold: Double): Double {
return this.filter { it.value > threshold }
.values.sum()
}
// Использование
val prices = mapOf("apple" to 2.5, "banana" to 1.8, "avocado" to 3.2, "berry" to 4.0)
val fruitsStartingWithA = prices.sumWhereKeyMatches("^a.*".toRegex())
println(fruitsStartingWithA) // 5.7 (apple + avocado)
val expensiveFruits = prices.sumWhereValueGreaterThan(3.0)
println(expensiveFruits) // 7.2 (avocado + berry)
3. Infix-нотация для читаемости
Можно использовать infix-функции для создания DSL-подобного синтаксиса:
infix fun Map<String, Number>.sumWhere(condition: (String, Number) -> Boolean): Double {
return this.filter { condition(it.key, it.value) }
.values.sumOf { it.toDouble() }
}
// Использование с infix-нотацией (если функция принимает лямбду последним параметром)
val scores = mapOf("John" to 85, "Jane" to 92, "Bob" to 76, "Alice" to 95)
val highScores = scores.sumWhere { name, score ->
score.toInt() > 80 && name.startsWith("J")
}
println(highScores) // 177.0 (John + Jane)
4. Комбинирование с другими extension-функциями
// Дополнительные полезные extensions
fun <K> Map<K, Int>.averageByPredicate(predicate: (Map.Entry<K, Int>) -> Boolean): Double {
val filtered = this.entries.filter(predicate)
return if (filtered.isEmpty()) 0.0 else filtered.sumOf { it.value } / filtered.size.toDouble()
}
fun Map<String, List<Int>>.sumOfAllWhere(predicate: (String) -> Boolean): Int {
return this.filter { predicate(it.key) }
.flatMap { it.value }
.sum()
}
// Использование
val teamScores = mapOf(
"developers" to listOf(85, 90, 78),
"managers" to listOf(92, 88),
"qa" to listOf(76, 82, 79)
)
val devScoresSum = teamScores.sumOfAllWhere { it.contains("dev") }
println(devScoresSum) // 253 (85+90+78)
Преимущества такого подхода:
- Читаемость кода:
map.sumByPredicate { ... }читается практически как обычное предложение - Переиспользование: один раз написали - используем во всем проекте
- Типобезопасность: компилятор проверит типы на этапе компиляции
- Интеграция с Kotlin stdlib: отлично сочетается со стандартными функциями вроде
filter,map,sumOf - Поддержка IDE: автодополнение и подсказки параметров работают отлично
Рекомендации по использованию:
- Именование: используйте понятные имена в стиле
sumBy...,sumWhere...,totalFor... - Производительность: для больших коллекций учитывайте, что некоторые реализации создают промежуточные коллекции
- Null-безопасность: предусматривайте обработку null-значений если коллекция может содержать null
- Generic-типы: используйте обобщенные типы для максимальной гибкости, но сохраняйте контекстную полезность
Альтернатива без extension-функций (для сравнения):
// Без extension
val sum = map.entries.filter { it.value > 10 }.sumOf { it.value }
// С extension - более выразительно
val sum = map.sumWhereValueGreaterThan(10)
Вывод: Использование extension-функций для работы с коллекциями "ключ-значение" в Kotlin не только возможно, но и является рекомендуемой практикой, соответствующей философии языка — писать выразительный, безопасный и поддерживаемый код. Это позволяет создавать предметно-ориентированный язык для вашей конкретной предметной области, что особенно ценно в больших проектах.