Как поменять карту в Котлине?

Я пытаюсь перевернуть карту в Котлине. До сих пор я придумал:

mapOf("foo" to 42)
  .toList()
  .map { (k, v) -> v to k }
  .toMap()

Есть ли лучший способ сделать это без посредников (middlelist)?

6 ответов

Решение

Так как Map состоит из Entry и это не Iterable, но вместо этого вы можете использовать записи Map #, они будут сопоставлены с Map#entrySet для создания резервной копии Set<Entry>, например:

val reversed = map.entries.associateBy({ it.value }) { it.key }

ИЛИ используйте Iterable # associate, но это создаст дополнительные Pair s.

val reversed = map.entries.associate{(k,v)-> v to k}

ИЛИ вместо этого используйте Map#forEach. например:

val reversed = mutableMapOf<Int, String>().also {
    //     v-- use `forEach` here     
    map.forEach { (k, v) -> it.put(v, k) } 
}.toMap()
// ^--- you can add `toMap()` to create an immutable Map.

Вот простая функция расширения, которая переворачивает карту - без генерации ненужного мусора (например, пар, промежуточных структур данных и ненужных замыканий)

fun <K, V> Map<K, V>.reversed() = HashMap<V, K>().also { newMap ->
    entries.forEach { newMap.put(it.value, it.key) }
}

Обратите внимание, что apply встраивается, и entries.forEach также встроен (что не то же самое для Map::forEach)

Если вам нужно перевернуть мульти-карту, например m: Map<K, List<V>> к Map<V, List<K>> ты можешь сделать

m
  .flatMap { it.value.map { oneValue -> oneValue to it.key } }
  .groupBy({ it.first }, { it.second })
  .toMap()

В последовательности,

  • mapOf('a' to listOf('b', 'c'), 'd' to listOf('b'))получает плоское отображение на последовательность вроде
  • listOf('b' to 'a', 'c' to 'a', 'b' to 'd') который группируется в
  • listOf('b' to listOf('a', 'd'), 'c' to listOf('a')) который затем преобразуется в карту.

Вероятно, это создает промежуточные объекты.

Если ваша карта не является отображением 1-1 и вы хотите, чтобы инверсия была списком значений:

mapOf(1 to "AAA", 2 to "BBB", 3 to "BBB").toList()
        .groupBy { pair -> pair.second } // Pair<Int, String>
        .mapValues { entry -> 
          entry.value.map { it.first } // Entry<String, List<Pair<Int, String>>
        }

Я все еще изучаю все входы и выходы Kotlin, но у меня было то же самое требование, и с Kotlin 1.2 кажется, что вы можете перебирать Map и таким образом отображать map() прямо так:

@Test
fun testThatReverseIsInverseOfMap() {
    val intMap = mapOf(1 to "one", 2 to "two", 3 to "three")
    val revMap = intMap.map{(k,v) -> v to k}.toMap()
    assertTrue(intMap.keys.toTypedArray() contentEquals revMap.values.toTypedArray())
    assertTrue(intMap.values.toTypedArray() contentEquals revMap.keys.toTypedArray())
}

Это мой взгляд на карту 1:1

          private fun <K, V> Map<K, V>.reverseOneToOneMap(): Map<V, K> {
        val result = this.entries.associateBy({ it.value }) { it.key }
        if (result.size != this.size) {
            throw RuntimeException("Map must be 1:1")
        }
        return result
    }
Другие вопросы по тегам