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

时间:2018-05-01 19:48:33

标签: scala scala-collections

我正在努力解决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 result = map.filter(p)

    map - = result.keys

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

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

  3. 我跑了一些测试。正如预期的那样,当删除的条目数量与原始地图的大小相比较小时,第一种解决方案实际上更快。两个解决方案大致同时运行的拐点是谓词将原始地图分成两半。第二个解决方案似乎并不依赖于此,但第一个解决方案显然确实如此。两者都是O(n),所以也许我在这里太挑剔了。我希望我能分开两个答案之间的勾选标记。感谢Don Branson和流氓一人。

3 个答案:

答案 0 :(得分:5)

如果您可以返回新的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)

答案 1 :(得分:1)

解决此问题的惯用方法是使用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)

但是,这两个调用可以组合成一个分区调用:

partition()

最重要的是,更新可变集合是将程序习惯用法应用于函数式语言,并为某些类型的错误打开了大门。返回新的,不可变的集合更符合功能上的惯用。

感谢rogue-one呼叫{{1}}作为选项。

答案 2 :(得分:0)

尝试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)