BiMap /双向HashMap在Kotlin
有没有kotlin的双向哈希映射? 如果不是 – 在kotlin中expression这个最好的方法是什么? 包括番石榴从那里得到BiMap感觉就像在一个很小的目标上用一个很大的枪射击 – 没有我能想象的解决方案现在感觉是正确的 – 我想到的最好的事情是写一个自定义的类
我也需要一个简单的BiMap
实现,所以决定创建一个名为bimap
的小库。
BiMap
的实现非常简单,但它包含一个棘手的部分,它是一组条目,键和值。 我将尝试解释一些实现的细节,但是你可以在GitHub上find完整的实现。
首先,我们需要为一个不可变和可变的BiMap
定义接口。
interface BiMap : Map { override val values: Set val inverse: BiMap } interface MutableBiMap : BiMap, MutableMap { override val values: MutableSet override val inverse: MutableBiMap fun forcePut(key: K, value: V): V? }
请注意, BiMap.values
返回一个Set
而不是一个Collection
。 当BiMap
已经包含一个给定的值时BiMap.put(K, V)
抛出一个exception。 如果你想用(K1, V1)
替代对(K1, V1)
和(K2, V2)
,你需要调用forcePut(K, V)
。 最后,你可能会得到一个逆BiMap
来通过值访问它的键。
BiMap
使用两个常规映射来实现:
val direct: MutableMap val reverse: MutableMap
可以通过交换direct
映射和reverse
映射来创建逆BiMap
。 我的实现提供了一个不变的bimap.inverse.inverse === bimap
但这不是必需的。
如前所述, forcePut(K, V)
方法可以用(K1, V1)
代替对(K1, V1)
和(K2, V2)
(K1, V2)
。 首先检查K1
的当前值是什么,并将其从reverse
映射中移除。 然后find值为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
并处理条目修改的Set
。 每个修改都发生在try
/ catch
块中,以便在抛出exception时BiMap
保持一致状态。 而且,迭代器和可变条目被包装在类似的类中。 不幸的是,它使迭代条目效率低得多,因为在每个迭代步骤中都会创建一个额外的MutableMap.MutableEntry
包装器。
那么你是对的 – 正如它在类似的问题中提到的Java“ 双向映射Java ”一样,Kotlin没有开箱即用的BiMap。
解决方法包括使用Guava
和使用两个通常的地图创建自定义类:
class BiMap() { private keyValues = mutableMapOf() private valueKeys = mutableMapOf() operator fun get(key: K) = ... operator fun get(value: V) = ... ... }
这个解决方案不应该比更复杂的解决方案更慢或者占用更多的内存。 虽然我不确定当K
和V
相同时会发生什么。
如果速度不是优先级,您可以使用扩展function:地图。 findKeyByValue (value)
fun Map.findKeyByValue(searchValue: Value): Key? { for ((key, value) in this) { if (value == searchValue) return key } return null }
使用番石榴最简洁的解决方案, 并创建一个扩展function,将地图变成一个BiMap。 这也遵循Kotlin其他地图转换的语义。 尽管Guava可能会有一些开销,但您将来可以灵活地添加更多的扩展函数包装器。 您可以随时删除番石榴,并将扩展function替换为其他实施。
首先声明你的扩展function。
fun
然后像这样使用它:
mutableMapOf("foo" to "bar", "me" to "you").toBiMap()