Scala:从元组列表构建映射,但如果存在矛盾的条目则失败

发布于 2024-11-06 21:17:15 字数 760 浏览 0 评论 0原文

我想这可能是一个常见的操作。所以也许它在 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 的实现构造了一个我想避免的新地图。

I think this might be a common operation. So maybe it's inside the API but I can't find it. Also I'm interested in an efficient functional/simple solution if not.

Given a sequence of tuples ("a" -> 1, "b" ->2, "c" -> 3) I want to turn it into a map. That's easy using TraversableOnce.toMap. But I want to fail this construction if the resulting map "would contain a contradiction", i.e. different values assigned to the same key. Like in the sequence ("a" -> 1, "a" -> 2). But duplicates shall be allowed.

Currently I have this (very imperative) code:

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)
}

Side Question

Is the final toMap really necessary? I get a type error when omitting it, but I think it should work. The implementation of toMap constructs a new map which I want to avoid.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

贪了杯 2024-11-13 21:17:15

与往常一样,在使用 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)
}

As always when working with Seq[A] the optimal solution performance-wise depends on the concrete collection type.
A general but not very efficient solution would be to fold over an 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
  }

If you restrict yourself to using List[A,B]s an optimized version would be:

@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
}

Additionally a less idiomatic version using mutable maps could be implemented like this:

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)
}
写下不归期 2024-11-13 21:17:15

这是一个缓慢失败的解决方案(如果创建整个地图然后丢弃它是可以的):

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
}

编辑:现在经过测试,它的输入速度(返回 true)比 Moritz 或我的直接解决方案快大约 2 倍。

Here is a fail-slowly solution (if creating the entire map and then discarding it is okay):

def uniqueMap[A,B](s: Seq[(A,B)]) = {
  val m = s.toMap
  if (m.size == s.length) Some(s) else None
}

Here is a mutable fail-fast solution (bail out as soon as the error is detected):

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
}

And here's an immutable fail-fast solution:

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]())
}

Edit: and here's a solution using put for speed (hopefully):

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
}

Edit: now tested, and it's ~2x as fast on input that works (returns true) than Moritz' or my straightforward solution.

不及他 2024-11-13 21:17:15

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

Scala 2.9 is near, so why not to take advantage of the combinations method (inspired by Moritz's answer):

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
空城之時有危險 2024-11-13 21:17:15

您还可以按如下方式使用 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 实现,您会发现它与建议的一些解决方案非常相似。

You can also use gourpBy as follows:

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

It's efficiency is competitive to the above solutions.
In fact if you examine the gourpBy implementation you will see that it is very similar to some of the solutions suggested.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文