【问题标题】:How to implement a generic algorithm for any Traversable in Scala?如何为Scala中的任何Traversable实现通用算法?
【发布时间】:2013-01-14 02:12:08
【问题描述】:

我正在实现一个通用算法来返回一个基于其他两个集合的集合。 问题可以简化为

def add[Repr <: Traversable[_]](coll1: Repr, coll2: Repr) = coll1 ++ coll2

当我将算法应用于我定义为的集合 A 时出现问题

class A[T] extends Iterable[(Int,T)] with IterableLike[(Int,T), A[T]] { ... }

即,A 的类型参数与继承的 Iterable 不同。 Map 使用了类似的方法。

地图示例:

scala> val m1 = Map("a" -> 1, "b" -> 1, "c" -> 1)
m1: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 1, c -> 1)

scala> val m2 = Map("a" -> 2, "c" -> 1)
m2: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 2, c -> 1)

以 m1 和 m2 作为参数应用 add 会产生一个 List:

scala> add(m1,m2)
res3: Traversable[Any] = List((a,1), (b,1), (c,1), (a,2), (c,1))

...虽然想要的结果类似于直接使用 ++ 方法:

scala> m1 ++ m2
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 2, b -> 1, c -> 1)

使用定义为的集合 B 不会发生此问题:

class B[T] extends Iterable[T] with IterableLike[T, B[T]] { ... }

例如,队列以类似的方式实现。

队列示例:

scala> val q1 = Queue(9,2,5)
q1: scala.collection.immutable.Queue[Int] = Queue(9, 2, 5)

scala> val q2 = Queue(7,3,1)
q2: scala.collection.immutable.Queue[Int] = Queue(7, 3, 1)

在 q1 和 q2 上应用 add 会得到想要的结果:

scala> add(q1,q2)
res4: Traversable[Any] = Queue(9, 2, 5, 7, 3, 1)

问题: 有没有办法实现add,使得结果与直接使用++方法时的结果一样,对于各种travesables(包括实现类似于Map的集合)?我一直在尝试在 A 类的伴随对象中实现隐式 CanBuildFrom,但没有成功。在我看来,问题在于算法,而不是集合实现,因为它也不适用于 Map。

【问题讨论】:

    标签: scala generics collections implicit-conversion scala-collections


    【解决方案1】:

    鉴于add 无非是TraversableLike.++ 的别名,第一步也是看++ 的签名:

    trait TraversableLike[+A, +Repr] extends ... {
      ...
      def ++:[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That
      ...
    }
    

    那么你要做的就是把this变成第一个参数,把that变成第二个参数:

    import collection.TraversableLike
    import collection.generic.CanBuildFrom
    def add[A, Repr, B >: A, That](coll1: TraversableLike[A, Repr], coll2: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
      coll1 ++ coll2
    }
    

    更新:我还调查了您需要做什么才能让A 相对于add 正常运行。就目前而言(没有做任何特别的事情),添加应用到 A 的实例上会返回 Iterable 而不是 A

    import collection.{IterableLike, TraversableLike}
    // Dummy `A` implementation, for illustration
    class A[T]( val inner: Seq[(Int, T)] ) extends Iterable[(Int,T)] with IterableLike[(Int,T), A[T]] {
      def iterator: Iterator[(Int, T)] = inner.iterator
      override protected[this] def newBuilder: scala.collection.mutable.Builder[(Int, T),A[T]] = ???
      def :+(elem: (Int, T) ): A[T] = new A[T]( inner :+ elem )
    }
    object A  {
      def apply[T]( elems: (Int, T)* ) = new A( elems )
    }
    val a1 = A( 1-> "one", 2 -> "two" )
    val a2 = A( 3-> "three", 4 -> "four", 5 -> "five" )
    add(a1, a2)
    

    结果是:

    res0: Iterable[(Int, String)] = List((1,one), (2,two), (3,three), (4,four), (5,five))
    

    这是我通过 CanBuildFrom 发帖的结果。我不能保证这是最好的例子,但它确实有效(我的意思是我们在调用add 时得到一个A 作为结果):

    import collection.IterableLike
    import collection.generic.CanBuildFrom
    import collection.mutable.Builder
    
    class A[T]( val inner: Seq[(Int, T)] ) extends Iterable[(Int,T)] with IterableLike[(Int,T), A[T]] {
      def iterator: Iterator[(Int, T)] = inner.iterator
      override protected[this] def newBuilder: scala.collection.mutable.Builder[(Int, T),A[T]] = new A.ABuilder[T]
      def :+(elem: (Int, T) ): A[T] = new A[T]( inner :+ elem )
    }
    object A  {
      private val _empty = new A[Nothing]( Nil )
      def empty[T]: A[T] = _empty.asInstanceOf[A[T]]
      def apply[T]( elems: (Int, T)* ) = new A( elems )
    
      class ABuilder[T] extends Builder[(Int,T), A[T]] {
        protected var elems: A[T] = empty
        def +=(x: (Int, T)): this.type = { elems = elems :+ x; this }
        def clear() { elems = empty }
        def result: A[T] = elems
      }
    
      implicit def canBuildFrom[T]: CanBuildFrom[A[_], (Int,T), A[T]] = new CanBuildFrom[A[_], (Int,T), A[T]] {
        def apply(from: A[_]) = apply()
        def apply() = new ABuilder[T]
      }
    }
    

    现在结果是:

    res0: A[String] = ((1,one), (2,two), (3,three), (4,four), (5,five))
    

    【讨论】:

    • 我很高兴:D。我添加了一个部分来解决将add 的结果应用于您的自定义集合A 时的键入问题。
    猜你喜欢
    • 2020-05-16
    • 2021-07-03
    • 1970-01-01
    • 2016-01-19
    • 2016-05-06
    • 2021-04-10
    • 1970-01-01
    • 2018-03-02
    • 2015-05-22
    相关资源
    最近更新 更多