BiMap / 2-way hashmap в Котлине

Существует двунаправленная хэш-карта для kotlin? Если нет - как лучше выразить это в котлине? Включая гуаву, чтобы получить BiMap оттуда, вы чувствуете, как стреляете из очень большого пистолета по очень маленькой цели - ни одно решение, которое я могу себе представить, в настоящее время не кажется правильным - лучшее, что я имею в виду, это написать собственный класс для него

4 ответа

Решение

Мне нужно простое BiMap Реализация тоже так решил создать небольшую библиотеку под названием bimap,

Реализация BiMap это довольно просто, но он содержит сложную часть, которая представляет собой набор записей, ключей и значений. Я попытаюсь объяснить некоторые детали реализации, но вы можете найти полную реализацию на GitHub.

Во-первых, нам нужно определить интерфейсы для неизменяемого и изменяемого BiMap s.

interface BiMap<K : Any, V : Any> : Map<K, V> {
  override val values: Set<V>
  val inverse: BiMap<V, K>
}

interface MutableBiMap<K : Any, V : Any> : BiMap<K, V>, MutableMap<K, V> {
  override val values: MutableSet<V>
  override val inverse: MutableBiMap<V, K>

  fun forcePut(key: K, value: V): V?
}

Пожалуйста, обратите внимание, что BiMap.values возвращает Set вместо Collection, Также BiMap.put(K, V) выдает исключение, когда BiMap уже содержит данное значение. Если вы хотите заменить пары (K1, V1) а также (K2, V2) с (K1, V2) тебе нужно позвонить forcePut(K, V), И, наконец, вы можете получить обратное BiMap получить доступ к его ключам по значениям.

BiMap реализован с использованием двух регулярных карт:

val direct: MutableMap<K, V>
val reverse: MutableMap<V, K>

Обратное BiMap может быть создан просто поменяв direct и reverse карты. Моя реализация предоставляет инвариант bimap.inverse.inverse === bimap но это не обязательно.

Как упоминалось ранее forcePut(K, V) метод может заменить пары (K1, V1) а также (K2, V2) с (K1, V2), Сначала он проверяет, что текущее значение для K1 и удаляет его из reverse карта. Затем он находит ключ для значения V2 и удаляет его из direct карта. И тогда метод вставляет данную пару в обе карты. Вот как это выглядит в коде.

override fun forcePut(key: K, value: V): V? {
  val oldValue = direct.put(key, value)
  oldValue?.let { reverse.remove(it) }
  val oldKey = reverse.put(value, key)
  oldKey?.let { direct.remove(it) }
  return oldValue
}

Реализации Map а также MutableMap Методы довольно просты, поэтому я не буду подробно описывать их здесь. Они просто выполняют операцию на обеих картах.

Самая сложная часть entries, keys а также values, В моей реализации я создаю Set который делегирует все вызовы методов direct.entries и обрабатывать изменения записей. Каждая модификация происходит в try / catch блок так, чтобы BiMap остается в согласованном состоянии, когда выдается исключение. Более того, итераторы и изменяемые записи заключены в похожие классы. К сожалению, это делает итерацию по записям намного менее эффективной, потому что дополнительная MutableMap.MutableEntry Оболочка создается на каждом шаге итерации.

Если скорость не является приоритетом, вы можете создать функцию расширения: map.getKey(value)

/**
 * Returns the first key corresponding to the given [value], or `null`
 * if such a value is not present in the map.
 */
fun <K, V> Map<K, V>.getKey(value: V) =
    entries.firstOrNull { it.value == value }?.key

FWIW, вы можете получить инверсию карты в Kotlin, используя функцию расширения:

fun <K, V> Map<K, V>.inverseMap() = map { Pair(it.value, it.key) }.toMap()

В map оператор может использоваться для итерации по List пар "ключ-значение" в Map, затем преобразовать обратно в карту, используя .toMap().

Ну, вы правы - как сказано в аналогичном вопросе для Java " Двунаправленная карта в Java?", У Kotlin нет BiMap из коробки.

Обходные пути включают использование Guava и создание собственного класса с использованием двух обычных карт:

class BiMap<K, V>() {
    private keyValues = mutableMapOf<K, V>()
    private valueKeys = mutableMapOf<V, K>()

    operator fun get(key: K) = ...
    operator fun get(value: V) = ...

    ...
}

Это решение не должно быть медленнее или занимать больше памяти, чем более сложное. Хотя я не уверен, что происходит, когда K такой же как V,

Самое чистое решение использовать Guava и создать функцию расширения, которая превращает Map в BiMap. Это следует семантике других преобразований Kotlin Map. Хотя у Guava могут быть небольшие накладные расходы, вы получаете гибкость, чтобы добавить больше расширений функций-оболочек в будущем. Вы всегда можете удалить Guava в будущем и заменить функцию расширения другой реализацией.

Сначала объявите вашу функцию расширения.

fun <K, V> Map<K, V>.toBiMap() = HashBiMap.create(this)

Тогда используйте это так:

mutableMapOf("foo" to "bar", "me" to "you").toBiMap()

Другие вопросы по тегам