将从可变映射中删除的元素收集到第二个可变映射中的惯用方法

Idiomatic way to collect elements removed from a mutable map into a second mutable map

我正在为 Scala 中缺少 Java 的 Iterator.remove() 而苦恼。特别是,我想在一次遍历大型可变映射时,删除满足谓词的元素并将它们收集到另一个可变映射中。

这是我正在尝试做的事情:

def main(args: Array[String]) {
  val map = new TrieMap[String, Integer]();
  map += "one" -> 1
  map += "two" -> 2

  // Remove all elems whose value is > 1 and put them in val removed.
  val removed = removeIf(map, _._2 > 1) 
}

def removeIf(
    map: mutable.Map[String, Integer], 
    p: ((String, Integer)) =>  Boolean): mutable.Map[String, Integer] = {

  val result = mutable.Map[String, Integer]()
  val iter = map.iterator
  while (iter.hasNext) {
    val elem = iter.next()
    if ( p(elem) ) {
      iter.remove()  // Error
      result += elem
    }
  }
  result
}

出于某些合理的原因,Scala 的 Iterator,即使在可变集合上,也没有实现 remove()

编辑 下面提供的两个解决方案是:

  1. 不用担心第二遍的成本,使用 filter() 然后 --= 删除过滤的条目:

    val 结果 = map.filter(p)

    地图--=result.keys

  2. 使用分区并将新映射重新分配给旧变量:

    (result, newMap) = map.partition({case (k,v) => ... })

我运行一些测试。正如预期的那样,第一个解决方案实际上更快,在删除条目的数量与原始地图的大小相比较小的情况下。拐点,其中两个解决方案 运行 大致相同的时间是谓词将原始地图分成两半的时候。第二种解决方案似乎不依赖于此,但第一种显然依赖于此。两者都是 O(n),所以也许我在这里太挑剔了。我希望我可以在两个答案之间拆分复选标记。多亏了 Don B运行儿子和 rogue-one.

一种惯用的方法是使用 filterNot() / filter():

def main(args: Array[String]) {
  val map = new TrieMap[String, Integer]();
  map += "one" -> 1
  map += "two" -> 2

  val removed = map.filterNot(_._2 > 1)
  val newMap = map.filter(_._2 > 1)
}

但是,这两个调用可以合并为一个分区调用:

val (newMap, removed) = map.partition(_._2 > 1)

归根结底,更新可变集合是将过程惯用语应用到函数式语言上,并为某些类型的错误打开了大门。返回新的、不可变的集合在功能上更符合惯用语。

感谢 rogue-one 呼唤 partition() 作为一个选项。

如果您可以返回新的 Map 对象,则以下方法有效。该解决方案使用 partition 集合方法并且仅使用一次传递。

scala> val map = TrieMap[String, Integer]("one" -> 1, "two" -> 2)
map: scala.collection.concurrent.TrieMap[String,Integer] = TrieMap(two -> 2, one -> 1)

scala> val (newMap, removed) = map.partition({case(_, x) => x > 1})
newMap: scala.collection.concurrent.TrieMap[String,Integer] = TrieMap(two -> 2)
removed: scala.collection.concurrent.TrieMap[String,Integer] = TrieMap(one -> 1)

尝试对谓词进行 groupBy,您将得到一个包含两个键的映射:对于应该保留的键为 true,对于应该删除的键为 false。

  val p: ((String, Int)) =>  Boolean = (_._2>1)
  private val booleanToStringToInt = Map[String, Int]("one" -> 1, "two" -> 2).groupBy(p)
  val remain =  booleanToStringToInt(true)
  val removed = booleanToStringToInt(false)