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()