2015-11-18 2 views
3

Я хотел бы иметь функцию groupByIndex, которая группирует значения на основе их индекса (а не значения). Конкретное определение метода для Vector[A] может выглядеть следующим образом:Как создать универсальную функцию groupByIndex?

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 ", чтобы дать этот метод всем классам, которые должны его поддерживать, т.е. классы, которые реализуют zipWithIndex и groupBy. Эти два метода определены в GenIterableLike (zipWithIndex) и GenTraversableLike/TraversableLike (groupBy).

С учетом всего этого, я пытался имитировать определения метод 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))) 

Как а) упростить этот метод и б) заставить его работать без необходимости указывать общие параметры?

ответ

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

Это не так хорошо, как другой ответ, но если вы не заботитесь о том, что вы создаете, один из способов, чтобы упростить и избежать строительств архивных коллекций:

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

Диапазон - это доброжелательный способ избежать размера прохода.

Смежные вопросы