如何使用用户定义的相等性实现一个集合

15
这个问题与此问题相关:在Scala中是否可以创建一个类似于Set的类(即它扩展了Set特征),其中用于定义包含关系的相等性是由用户定义而不是==
测试其是否有效的一种方法是检查filter是否返回相同的集合类型。
// typeclass for equality
trait Equals[T] {
  def isEqual(t1: T, t2: T): Boolean
}

// an object representing plane coordinates
case class Coordinate(i: Int, j: Int)

// an equality saying that 2 coordinates are equal if they are on 
// the same horizontal line
implicit def horizontalEquality: Equals[Coordinate] = new Equals[Coordinate] {
   def isEqual(t1: Coordinate, t2: Coordinate) = t1.i == t2.i
}

// we create an EqualitySet[T] where T must verify [T : Equals]     
val set = EqualitySet[Coordinate]()

// set2 must be of type EqualitySet[Coordinate]
val set2 = set.filter(_.i > 0)

1个回答

11

我们在一次与 Miles Sabin(@milessabin)的 Scala 培训中作为一个小组创建了这个解决方案。

import scala.collection.mutable.ListBuffer
import scala.collection.generic.CanBuildFrom
import scala.collection.SetLike
import scala.collection.mutable.Builder

/**
 * we extend Set[T] to provide the Set-like interface
 * we extends SetLike[T, EqualitySet[T]] to specify that Set methods will return
 *   instances of type EqualitySet (and not simply Set)
 */
trait EqualitySet[T] extends Set[T] with SetLike[T, EqualitySet[T]] { outer =>
  /** we need to provide an Equals[T] instance to create an EqualitySet[T] */
  implicit def equality: Equals[T]  

  /** our internal implementation as a list of elements */
  protected val set = ListBuffer[T]()

  /** we need to implements those 4 methods */
  def contains(t: T) = set.exists(equality.isEqual(_, t))
  def +(t: T) = { if (!contains(t)) set += t; this }
  def -(t: T) = { set -= t; this }
  def iterator = set.iterator

  /** we must be able to provide an empty set with the proper equality definition */
  override def empty = new EqualitySet[T] { 
    override def equality = outer.equality
  }
}  

/** 
 * Companion object for the EqualitySet class
 */
object EqualitySet {

  /** 
   * this implicit is absolutely necessary to be able to preserve the resulting
   * collection type when calling `filter`
   */
  implicit def canBuildFrom[T] = new CanBuildFrom[EqualitySet[T], T, EqualitySet[T]] {
    def apply(from: EqualitySet[T]): Builder[T, EqualitySet[T]] = 
      new Builder[T, EqualitySet[T]] {
        // use a ListBuffer internally to accumulate elements
        private val elems = ListBuffer[T]()
        def +=(t: T) = { 
          if (!elems.exists(from.equality.isEqual(_, t))) elems += t
          this
        } 
        def clear() = elems.clear

        // when we finish building the collection
        // we can return an EqualitySet with the original equality relation
        def result() = new EqualitySet[T] {
          override val set = elems
          override def equality = from.equality
        }
     }
     def apply(): Builder[T, EqualitySet[T]] = 
       sys.error("this can't be implemented, because no equality instance is provided")
  }

  /** @return an EqualitySet for a type T having an Equals instance */
  def apply[T : Equals](ts: T*) = {
    var set  = new EqualitySet[T] {
      def equality = implicitly[Equals[T]]
    }.empty
    ts.foreach { t => set += t }
    set
  }
}

然后,当我们使用上述代码时,我们得到:
scala> val set = EqualitySet[Coordinate](Coordinate(-1, 2), 
                                         Coordinate(-1, 3), 
                                         Coordinate(1, 4))

set: java.lang.Object with test.EqualitySet[Coordinate] = 
     Set(Coordinate(-1,2)
         Coordinate(1,4))

scala> val set2 = set.filter(_.i > 0)

// still an EqualitySet[Coordinate] \o/ */
set2: test.EqualitySet[Coordinate] = Set(Coordinate(1,4))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接