【问题标题】:BiMap / 2-way hashmap in KotlinKotlin 中的 BiMap / 2-way hashmap
【发布时间】:2016-07-22 06:56:24
【问题描述】:

kotlin 有双向哈希表吗? 如果不是 - 在 kotlin 中表达这一点的最佳方式是什么? 包括番石榴以从那里获取 BiMap 感觉就像用一把非常大的枪射击一个很小的目标 - 我可以想象目前没有正确的解决方案 - 我想到的最好的事情是为它编写一个自定义类

【问题讨论】:

  • 为什么说使用番石榴“感觉就像用很大的枪射击”?许多图书馆都使用番石榴。我看不出有任何理由重新实现已经现成的东西。如果您担心 JAR 大小,那么您可以使用 ProGuard 或复制必要的类。
  • 是的,但即使我使用 proguard 删除,我也会在构建时受到大型库的惩罚
  • 如果你发现你的构建有和没有番石榴,我怀疑你会发现差异很小。
  • 不在安卓环境中

标签: kotlin bimap


【解决方案1】:

如果速度不是优先考虑因素(O(n) 复杂度),您可以创建一个扩展函数: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

【讨论】:

  • 你最终得到了一个复杂度为 O(1) 的 get 方法和一个复杂度为 O(n) 的 getKey 方法,我认为这不是一个好主意。一个更一致的解决方案是创建一个 Bimap,它使用两个地图,就像另一个答案建议的那样。
  • 他写道“如果速度不是优先事项”...
【解决方案2】:

FWIW,您可以使用扩展函数在 Kotlin 中获取地图的倒数:

fun &lt;K, V&gt; Map&lt;K, V&gt;.inverseMap() = map { Pair(it.value, it.key) }.toMap()

map 运算符可用于迭代 Map 中键值对的 List,然后使用 .toMap() 转换回映射。

【讨论】:

  • 这不是 OP 要求的双映射,即反向映射不会与原始映射同步。但是,如果您只需要一个快速、一次性的逆映射,这是一个非常优雅的解决方案!
【解决方案3】:

使用 Guava 的最简洁解决方案创建一个将 Map 转换为 BiMap 的扩展函数。这也遵循 Kotlin 的其他 Map 转换的语义。尽管 Guava 可能会有一些开销,但您可以灵活地在将来添加更多扩展函数包装器。将来您可以随时删除 Guava,并将扩展功能替换为其他实现。

首先声明你的扩展函数。

fun &lt;K, V&gt; Map&lt;K, V&gt;.toBiMap() = HashBiMap.create(this)

然后像这样使用它:

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

【讨论】:

    【解决方案4】:

    我也需要一个简单的BiMap 实现,因此决定创建一个名为bimap 的小库。

    BiMap 的实现非常简单,但它包含一个棘手的部分,即一组条目、键和值。我将尝试解释实现的一些细节,但您可以在 GitHub 上找到完整的实现。

    首先,我们需要为不可变和可变BiMaps 定义接口。

    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 已经包含给定值时,BiMap.put(K, V) 也会引发异常。如果您想用(K1, V2) 替换对(K1, V1)(K2, V2),您需要调用forcePut(K, V)。最后你可能会得到一个反向的BiMap 来按值访问它的键。

    BiMap 是使用两个常规映射实现的:

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

    可以通过交换directreverse 映射来创建反向BiMap。我的实现提供了一个不变的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
    }
    

    MapMutableMap 方法的实现非常简单,所以我不会在这里提供它们的详细信息。他们只是在两个地图上执行操作。

    最复杂的部分是entrieskeysvalues。在我的实现中,我创建了一个Set,它将所有方法调用委托给direct.entries,并处理条目的修改。每次修改都发生在 try/catch 块中,以便在引发异常时 BiMap 保持一致状态。此外,迭代器和可变条目被包装在类似的类中。不幸的是,它使得对条目的迭代效率大大降低,因为在每个迭代步骤中都会创建一个额外的 MutableMap.MutableEntry 包装器。

    【讨论】:

    • 非常感谢详细的回答和库
    • 不客气!正在与jcenter联动,可能需要一天左右的时间。明天它应该会在 jcenter 上可用。
    【解决方案5】:

    嗯,你是对的 - 正如 Java "Bi-directional Map in 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) = ...
    
        ...
    }
    

    此解决方案不应比更复杂的解决方案更慢或占用更多内存。虽然我不确定当KV 相同时会发生什么。

    【讨论】:

    • 不幸的是,创建BiMap 并不是那么简单。考虑entrieskeysvalues
    • @Michael 这就是... 的用途
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    相关资源
    最近更新 更多