【问题标题】:implement a multiset/bag as Scala collection将 multiset/bag 实现为 Scala 集合
【发布时间】:2013-02-28 18:03:16
【问题描述】:

this question 的启发,我想在Scala 中实现Multiset。我想要一个MultiSet[A] 到:

  • 支持加、删、并、交、差
  • 成为A => Int,提供每个元素的计数

这是一种方法,扩展Set

import scala.collection.immutable.Map
import scala.collection.immutable.Set
import scala.collection.SetLike
import scala.collection.mutable.Builder

class MultiSet[A](private val counts: Map[A, Int] = Map.empty[A, Int])
extends SetLike[A, MultiSet[A]] with Set[A] {
  override def +(elem: A): MultiSet[A] = {
    val count = this.counts.getOrElse(elem, 0) + 1
    new MultiSet(this.counts + (elem -> count)) 
  }
  override def -(elem: A): MultiSet[A] = this.counts.get(elem) match {
    case None => this
    case Some(1) => new MultiSet(this.counts - elem)
    case Some(n) => new MultiSet(this.counts + (elem -> (n - 1)))
  }
  override def contains(elem: A): Boolean = this.counts.contains(elem)
  override def empty: MultiSet[A] = new MultiSet[A]
  override def iterator: Iterator[A] = {
    for ((elem, count) <- this.counts.iterator; _ <- 1 to count) yield elem
  }
  override def newBuilder: Builder[A,MultiSet[A]] = new Builder[A, MultiSet[A]] {
    var multiSet = empty
    def +=(elem: A): this.type = {this.multiSet += elem; this}
    def clear(): Unit = this.multiSet = empty
    def result(): MultiSet[A] = this.multiSet
  }
  override def seq: MultiSet[A] = this
}

object MultiSet {
  def empty[A]: MultiSet[A] = new MultiSet[A]
  def apply[A](elem: A, elems: A*): MultiSet[A] = MultiSet.empty + elem ++ elems
  def apply[A](elems: Seq[A]): MultiSet[A] = MultiSet.empty ++ elems
  def apply[A](elem: (A, Int), elems: (A, Int)*) = new MultiSet((elem +: elems).toMap)
  def apply[A](elems: Map[A, Int]): MultiSet[A] = new MultiSet(elems)
}

扩展Set 很好,因为这意味着MultiSet 自动获取联合、差异等的定义。以下所有内容都将成立:

// add
assert(
  MultiSet("X" -> 3, "Y" -> 1) + "X" ===
    MultiSet("X" -> 4, "Y" -> 1))
assert(
  MultiSet("X" -> 3, "Y" -> 1) + "Z" ===
    MultiSet("X" -> 3, "Y" -> 1, "Z" -> 1))

// remove
assert(
  MultiSet("a" -> 2, "b" -> 5) - "b" ===
    MultiSet("a" -> 2, "b" -> 4))
assert(
  MultiSet("a" -> 2, "b" -> 5) - "c" ===
    MultiSet("a" -> 2, "b" -> 5))

// add all
assert(
  MultiSet(10 -> 1, 100 -> 3) ++ MultiSet(10 -> 1, 1 -> 7) ===
    MultiSet(100 -> 3, 10 -> 2, 1 -> 7))

// remove all
assert(
  MultiSet("a" -> 2, "b" -> 5) -- MultiSet("a" -> 3) ===
    MultiSet("b" -> 5))

但是,我必须重写一些继承的方法,例如 unionintersect,因为它们会对多重集做错误的事情,例如以下不成立:

// union (takes max of values)
assert(
  MultiSet(10 -> 5, 1 -> 1).union(MultiSet(10 -> 3, 1 -> 7)) ===
    MultiSet(10 -> 5, 1 -> 7))

// intersection (takes min of values)
assert(
  MultiSet(10 -> 5, 100 -> 3).intersect(MultiSet(10 -> 1, 1 -> 7)) ===
    MultiSet(10 -> 1))

扩展Set 的另一个问题是,我不能让MultiSet 成为A =&gt; Int,因为我会收到错误:illegal inheritance; class MultiSet inherits different type instances of trait Function1: A =&gt; Int and A =&gt; Boolean。我可以通过声明一个单独的方法来解决这个问题,比如说,count,但我真的更喜欢这个类只是一个A =&gt; Int

另一种方法是从Map[A, Int] 继承,这将给我我想要的A =&gt; Int,但是我必须定义我自己的所有++-- 等,因为在@ 987654343@ 这些将在 (A, Int) 对上定义,但对于多重集,它们需要在 As 上定义。

我想第三种方法是放弃SetMap,而只实现Iterable 的新子类或其他。

你会推荐什么?在 Scala 集合框架中放置 MulitSet 的最佳方式是什么?

【问题讨论】:

  • 棘手的问题。我想归根结底是你的MultiSetA =&gt; Int 还是A =&gt; Boolean。对集合执行filter 之类的操作很常见,因此将其视为exists 方法。如果您的集合应该是 A =&gt; Int,您可能需要创建一个模仿 SetLikeMultiSetLike 特征。这样你就可以逃脱A =&gt; Boolean 的定义。

标签: scala scala-collections multiset


【解决方案1】:

但是,我必须重写一些继承的方法,例如 intersect,因为它们会对多重集做错误的事情

我认为你必须硬着头皮去做。

扩展 Set 的另一个问题是,我不能让 MultiSet 成为 A =&gt; Int

确实,您不能使用不同的类型参数继承两次相同的特征(此处为 Function1)。实际上在这种情况下,这不仅是一个令人讨厌的技术限制,而且这样做实际上并没有多大意义,因为在调用 apply 时,无法知道您要调用哪个重载:def apply( key: A ): Boolean 或 @ 987654325@?您无法判断,因为参数列表是相同的。

但是,您可以添加一个从 MultiSet[A]A =&gt; Int 的隐式转换。这样,MultiSet 在默认情况下被视为A =&gt; Boolean(作为任何集合),但可以在 nedded 时强制为A =&gt; Int(特别是它可以直接传递给期望A =&gt; Int 函数的函数)。

class MultiSet[A] ... {
  ...
  def count(elem: A): Int = counts.getOrElse( elem, 0 )
}
object MultiSet {
  ...
  implicit def toCountFunc[A]( ms: MultiSet[A] ): A => Int = { 
    (x: A) => ms.count( x )
  } 
}

REPL 中的一些测试:

scala> val ms = MultiSet("a" -> 2, "b" -> 5)
ms: MultiSet[String] = Set(a, a, b, b, b, b, b)
scala> ms("a")
res17: Boolean = true
scala> ms("c")
res18: Boolean = false

scala> def testExists( f: String => Boolean, keys: String *) {
     |   println( keys.map( f ).toList )
     | }
testExists: (f: String => Boolean, keys: String*)Unit
scala> testExists( ms, "a", "c" )
List(true, false)

scala> def testCounts( f: String => Int, keys: String *) {
     |   println( keys.map( f ).toList )
     | }
testCounts: (f: String => Int, keys: String*)Unit
scala> testCounts( ms, "a", "c" )
List(2, 0)

【讨论】:

  • 所以如果我仍然需要编写intersect 和隐式转换,也许我不应该从Set 继承,而是应该从Traversable 或类似的东西继承?
  • 在任何情况下你都必须实现intersect,那么为什么不保留Set 作为基本特征,覆盖intersect 并完成它呢?至少您的集合将是一个正确的 Set,这是您对 MultiSet 的最低期望。
  • 是的,我想我担心Set 上会有更多我没想到的方法也会做错事。但这可能只是意味着我需要编写更多的测试用例。 ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-10
  • 2021-03-29
  • 2010-10-15
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多