我认为这可能是一种常见的操作。所以也许它在API内部,但我找不到它。如果不是,我也对有效的功能/简单解决方案感兴趣。
给定一系列元组("a" -> 1, "b" ->2, "c" -> 3)
我想把它变成一张地图。使用TraversableOnce.toMap
很容易。但是,如果结果地图“将包含矛盾”,即分配给同一个键的不同值,我想要使这种结构失败。就像序列("a" -> 1, "a" -> 2)
一样。但是应该允许重复。
目前我有这个(非常迫切的)代码:
def buildMap[A,B](in: TraversableOnce[(A,B)]): Option[Map[A,B]] = {
val map = new HashMap[A,B]
val it = in.toIterator
var fail = false
while(it.hasNext){
val next = it.next()
val old = map.put(next._1, next._2)
fail = old.isDefined && old.get != next._2
}
if(fail) None else Some(map.toMap)
}
最终toMap
真的有必要吗?省略它时会出现类型错误,但我认为它应该有效。 toMap
的实现构建了一个我想避免的新地图。
答案 0 :(得分:6)
与Seq[A]
一起使用时,最佳解决方案的性能取决于具体的集合类型。
一般但不是非常有效的解决方案是折叠Option[Map[A,B]]
:
def optMap[A,B](in: Iterable[(A,B)]): Option[Map[A,B]] =
in.iterator.foldLeft(Option(Map[A,B]())) {
case (Some(m),e @ (k,v)) if m.getOrElse(k, v) == v => Some(m + e)
case _ => None
}
如果您仅限于使用List[A,B]
,则优化版本为:
@tailrec
def rmap[A,B](in: List[(A,B)], out: Map[A,B] = Map[A,B]()): Option[Map[A,B]] = in match {
case (e @ (k,v)) :: tail if out.getOrElse(k,v) == v =>
rmap(tail, out + e)
case Nil =>
Some(out)
case _ => None
}
此外,使用可变地图的不太惯用的版本可以像这样实现:
def mmap[A,B](in: Iterable[(A,B)]): Option[Map[A,B]] = {
val dest = collection.mutable.Map[A,B]()
for (e @ (k,v) <- in) {
if (dest.getOrElse(k, v) != v) return None
dest += e
}
Some(dest.toMap)
}
答案 1 :(得分:4)
这是一个缓慢失败的解决方案(如果创建整个地图然后丢弃它就可以了):
def uniqueMap[A,B](s: Seq[(A,B)]) = {
val m = s.toMap
if (m.size == s.length) Some(s) else None
}
这是一个可变的快速失败解决方案(一旦检测到错误就挽救):
def uniqueMap[A,B](s: Seq[(A,B)]) = {
val h = new collection.mutable.HashMap[A,B]
val i = s.iterator.takeWhile(x => !(h contains x._1)).foreach(h += _)
if (h.size == s.length) Some(h) else None
}
这是一个不可改变的快速解决方案:
def uniqueMap[A,B](s: Seq[(A,B)]) = {
def mapUniquely(i: Iterator[(A,B)], m: Map[A,B]): Option[Map[A,B]] = {
if (i.hasNext) {
val j = i.next
if (m contains j._1) None
else mapUniquely(i, m + j)
}
else Some(m)
}
mapUniquely(s.iterator, Map[A,B]())
}
编辑:这是一个使用put
获得速度的解决方案(希望如此):
def uniqueMap[A,B](s: Seq[(A,B)]) = {
val h = new collection.mutable.HashMap[A,B]
val okay = s.iterator.forall(x => {
val y = (h put (x._1,x._2))
y.isEmpty || y.get == x._2
})
if (okay) Some(h) else None
}
编辑:现在经过测试,它的输入速度比Moritz或我直截了当的解决方案快2倍(返回true)。
答案 2 :(得分:1)
Scala 2.9即将到来,为什么不利用组合方法(灵感来自Moritz的回答):
def optMap[A,B](in: List[(A,B)]) = {
if (in.combinations(2).exists {
case List((a,b),(c,d)) => a == c && b != d
case _ => false
}) None else Some(in.toMap)
}
scala> val in = List(1->1,2->3,3->4,4->5,2->3)
in: List[(Int, Int)] = List((1,1), (2,3), (3,4), (4,5), (2,3))
scala> optMap(in)
res29: Option[scala.collection.immutable.Map[Int,Int]] = Some(Map(1 -> 1, 2 -> 3, 3 -> 4, 4 -> 5))
scala> val in = List(1->1,2->3,3->4,4->5,2->3,1->2)
in: List[(Int, Int)] = List((1,1), (2,3), (3,4), (4,5), (2,3), (1,2))
scala> optMap(in)
res30: Option[scala.collection.immutable.Map[Int,Int]] = None
答案 3 :(得分:1)
你也可以按如下方式使用gourpBy:
val pList = List(1 -> "a", 1 -> "b", 2 -> "c", 3 -> "d")
def optMap[A,B](in: Iterable[(A,B)]): Option[Map[A,B]] = {
Option(in.groupBy(_._1).map{case(_, list) => if(list.size > 1) return None else list.head})
}
println(optMap(pList))
它的效率与上述解决方案相比具有竞争力。 事实上,如果你检查gourpBy实现,你会发现它与一些建议的解决方案非常相似。