Как преобразовать список (List) в словарь (Map) в Kotlin?
Использование функции associateBy
Самый простой и идиоматичный способ преобразовать список в словарь (Map) в Kotlin — использовать функцию associateBy. Она принимает лямбду, которая возвращает ключ для каждого элемента. Если ключи уникальны, всё работает отлично.
data class User(val id: Int, val name: String)
val users = listOf(
User(1, "Alice"),
User(2, "Bob"),
User(3, "Charlie")
)
val userMap: Map<Int, User> = users.associateBy { it.id }
println(userMap) // {1=User(id=1, name=Alice), 2=User(id=2, name=Bob), 3=User(id=3, name=Charlie)}
Если вам нужно не весь объект, а какое-то его свойство (например, только имя), используйте associate:
val nameMap: Map<Int, String> = users.associate { it.id to it.name }
println(nameMap) // {1=Alice, 2=Bob, 3=Charlie}
Что делать с дубликатами ключей?
Если в списке есть элементы с одинаковыми ключами (например, два пользователя с id = 1), associateBy просто перезапишет предыдущее значение последним. Это может быть неочевидно и привести к багам. Чтобы явно обработать дубликаты, используйте groupBy (создаст Map<K, List<V>>) или associateBy с кастомным разрешением коллизий через associateBy + toMap.
val usersWithDuplicates = listOf(
User(1, "Alice"),
User(2, "Bob"),
User(1, "Charlie") // дубликат ключа 1
)
// Вариант 1: groupBy — сохраняет все значения в списке
val grouped: Map<Int, List<User>> = usersWithDuplicates.groupBy { it.id }
println(grouped) // {1=[User(id=1, name=Alice), User(id=1, name=Charlie)], 2=[User(id=2, name=Bob)]}
// Вариант 2: явное разрешение коллизий через toMap с merge-функцией
val mergedMap: Map<Int, User> = usersWithDuplicates.associateBy { it.id }.toMap()
// Или через fold (более гибко):
val customMap = usersWithDuplicates.fold(mutableMapOf<Int, User>()) { acc, user ->
acc.merge(user.id, user) { old, new -> old } // оставляем первого
acc
}
println(customMap) // {1=User(id=1, name=Alice), 2=User(id=2, name=Bob)}
Другие полезные функции
associateWith— если у вас есть список ключей, а значения вычисляются из них. Например:listOf(1,2,3).associateWith { "User$it" }.map+toMap()— работает, но менее читаемо:users.map { it.id to it }.toMap(). Не рекомендуется для простых случаев.withIndex()— если нужно создать Map с индексами в качестве ключей:users.withIndex().associateBy { it.index }.
Итог
Для 90% задач используйте associateBy — это коротко, понятно и эффективно. Если нужны дубликаты — groupBy. Если нужна кастомная логика слияния — fold или toMap с merge-функцией.
Альтернативный подход: использование toMap() с деструктуризацией
Если вы предпочитаете более функциональный стиль или работаете с парами, можно использовать комбинацию map и toMap(). Это особенно удобно, когда у вас уже есть список пар (например, из другого преобразования).
val users = listOf(
User(1, "Alice"),
User(2, "Bob"),
User(3, "Charlie")
)
// Явное создание пар
val pairList: List<Pair<Int, User>> = users.map { it.id to it }
val userMap: Map<Int, User> = pairList.toMap()
println(userMap) // {1=User(id=1, name=Alice), 2=User(id=2, name=Bob), 3=User(id=3, name=Charlie)}
Этот способ чуть длиннее, но он явно показывает, что вы сначала создаёте пары, а потом собираете их в Map. Минус — при дубликатах ключей toMap() также перезапишет последним значением, но вы можете использовать перегруженную версию toMap() с функцией слияния (начиная с Kotlin 1.1):
val usersWithDup = listOf(1 to "Alice", 2 to "Bob", 1 to "Charlie")
val merged = usersWithDup.toMap { old, new -> "$old & $new" } // склеиваем строки
println(merged) // {1=Alice & Charlie, 2=Bob}
Этот вариант полезен, когда вы хотите явно контролировать, что делать с дублирующимися ключами, не прибегая к groupBy.