如何创建通用的groupByIndex函数?

时间:2015-11-18 18:07:06

标签: scala generics

我希望groupByIndex函数根据索引(而不是值)对值进行分组。 Vector[A]的具体方法定义如下所示:

def groupByIndex[A, K](vector: Vector[A], f: Int => K): immutable.Map[K, Vector[(A, Int)]] = {
  vector.zipWithIndex.groupBy { case (elem, index) => f(index) }
}

在REPL中测试此函数确实给出了正确的结果:

scala> val vec = Vector.tabulate(4)(i => s"string ${i+1}")
vec: scala.collection.immutable.Vector[String] = Vector(string 1, string 2, string 3, string 4)

scala> groupByIndex(vec, i => i%2)
res2: scala.collection.immutable.Map[Int,Vector[(String, Int)]] = Map(1 -> Vector((string 2,1), (string 4,3)), 0 -> Vector((string 1,0), (string 3,2)))

现在,我想应用“enrich-my-library”模式将此方法提供给应该支持它的所有类,即实现zipWithIndexgroupBy的类。这两种方法在GenIterableLikezipWithIndex)和GenTraversableLike / TraversableLikegroupBy)中定义。

考虑到这一切,我试图模仿zipWithIndex的方法定义(这是有问题的)和groupBy来构建我自己的groupByIndex

implicit class GenIterableLikeOps[A, Repr](val iterable: GenIterableLike[A, Repr] with TraversableLike[A, Repr]) extends AnyVal {
  def groupByIndex[K, A1 >: A, That <: TraversableLike[(A1, Int), OtherRepr], OtherRepr](f: Int => K)(implicit bf: CanBuildFrom[Repr, (A1, Int), That]): immutable.Map[K, OtherRepr] = {
    val zipped = iterable.zipWithIndex
    zipped.groupBy{ case (elem, index) => f(index) }
  }
}

首先,这对我来说似乎太复杂了 - 有没有办法简化这个?例如,我们可以以某种方式删除第二个OtherRepr吗? (我无法。) 其次,如果没有明确指定泛型参数,我无法调用此函数。使用上面的示例我收到以下错误:

scala> vec.groupByIndex(i => i%2)
<console>:21: error: Cannot construct a collection of type scala.collection.TraversableLike[(String, Int),Nothing] with elements of type (String, Int) based on a collection of type scala.collection.immutable.Vector[String].
       vec.groupByIndex(i => i%2)
                       ^

scala> vec.groupByIndex[Int, String, Vector[(String, Int)], Vector[(String, Int)]](i => i%2)
res4: scala.collection.immutable.Map[Int,Vector[(String, Int)]] = Map(1 -> Vector((string 2,1), (string 4,3)), 0 -> Vector((string 1,0), (string 3,2)))

我如何a)简化这种方法,b)使其工作而不必指定通用参数?

2 个答案:

答案 0 :(得分:2)

您可以OtherThat替换That类型参数。这样你就摆脱了OtherThat并解决了必须指定泛型类型参数的问题。然后,编译器可以通过查看That的隐式值来解析CanBuildFrom[Repr, (A1, Int), That]

implicit class GenIterableLikeOps[A, Repr]
    (val iterable: GenIterableLike[A, Repr] with TraversableLike[A, Repr]) 
    extends AnyVal {

  def groupByIndex
      [K, A1 >: A, That <: TraversableLike[(A1, Int), That]]
      (f: Int => K)(implicit bf: CanBuildFrom[Repr, (A1, Int), That])
    : Map[K, That] = {
    val zipped = iterable.zipWithIndex
    zipped.groupBy{ case (elem, index) => f(index) }
  }
}

答案 1 :(得分:1)

这不如其他答案好,但是如果你不关心你正在构建什么,那么简化和避免构建压缩集合的一种方法是:

implicit class gbi[A](val as: Traversable[A]) extends AnyVal {
  def groupByIndex[K](f: Int => K) = (as, (0 until Int.MaxValue)).zipped.groupBy { case (x, i) => f(i) }
}

该范围是一种避免使用可穿越大小的良性方法。