实现一个multiset / bag作为Scala集合

时间:2013-02-25 10:40:39

标签: scala scala-collections multiset

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会自动获取union,difference等的定义。以下所有内容都将成立:

// 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 => Int,因为我会收到错误:illegal inheritance; class MultiSet inherits different type instances of trait Function1: A => Int and A => Boolean。我可以通过声明一个单独的count方法来解决这个问题,但我真的更喜欢这个类只是A => Int

另一种方法是从Map[A, Int]继承,这会给我我想要的A => Int,但是我必须定义我自己的所有++,{{1因为在--中,这些将在Map对上定义,但对于多集,它们需要在(A, Int) s上定义。

我想第三种方法是放弃ASet,只需实现Map或其他任何新的子类。

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

1 个答案:

答案 0 :(得分:1)

  

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

我认为你必须咬紧牙关并做到这一点。

  

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

实际上,您不能使用不同的类型参数继承两次相同的特征(此处为Function1)。实际上在这种情况下,这不仅仅是一个恼人的技术限制,但这样做实际上没有多大意义,因为在调用apply时,无法知道要调用哪个重载:{{1 }或def apply( key: A ): Boolean?你无法分辨,因为参数列表是相同的。

但是,您可以做的是添加从def apply( key: A ): IntMultiSet[A]的隐式转换。这样,默认情况下A => Int被视为MultiSet(与任何集合一样),但在嵌入时可以强制转换为A => Boolean(特别是它可以直接传递给函数)期待A => Int函数。)

A => Int

一些测试我的REPL:

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