如何使用用户定义的相等性实现 Set [英] How to implement a Set with a user-defined equality

查看:45
本文介绍了如何使用用户定义的相等性实现 Set的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题与这个问题有关:是否可以创建类似 Set 的Scala 中的类(意味着它扩展了 Set trait),其中用于定义包含关系的等式是由用户定义的,而不是 ==?

This question is related to this one: is it possible to create a Set-like class (meaning that it's extending the Set trait) in Scala where the equality used to define the containment relationship is defined by the user instead of being ==?

测试这是否真的有效的一种方法是检查 filter 是否返回相同的集合类型.

One way to test if this really works is to check whether filter returns the same collection type.

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

推荐答案

我们在与 Miles Sabin (@milessabin) 的 Scala 培训中以小组形式创建了此解决方案.

We created this solution as a group in a Scala training with Miles Sabin (@milessabin).

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

然后,当我们使用上面的代码时,我们得到:

Then, when we use the code above we get:

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

这篇关于如何使用用户定义的相等性实现 Set的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆