Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I add cross product based methods to scala collections?

hopefully this will be a simple question about library pimping (because other questions on that subject tend to generate answers beyond my current skill level).

All I want to do is map over the cross product of a collection with itself.

val distances = points.crossMap(_ distance _)  // points: List[Point3d]

So I attempted to pimp Traversable thus:

implicit def toSelfCrossMappable[A](xs: Traversable[A]) = new {
  def crossMap[B](f: (A, A) => B) = xs.flatMap(a => xs.map(f(a, _)))
}

But it doesn't work (it's not doing the implicit conversion) and I don't understand why not (I'm pretty new to scala). I also tried the method suggested in Enriching Scala collections with a method which left me with:

implicit def toSelfCrossMappable[A, C[A]](xs: C[A])(implicit c: C[A] => Traversable[A]) = new SelfCrossable[A, C[A]](xs)(c)

class SelfCrossable[A, C](xs: C)(implicit c: C => Traversable[A]) {
  def crossMap[B](f: (A, A) => B) = xs.flatMap(a => xs.map(f(a, _)))
}

, but that throws the same error as my (simpler looking) way.

What am I doing wrong here?

like image 580
teryret Avatar asked Dec 15 '22 11:12

teryret


2 Answers

It's not pretty, but this can be done with IsTraversableLike,

import scala.language.implicitConversions

import scala.collection.generic.{ CanBuildFrom, IsTraversableLike }
import scala.collection.GenTraversableLike

class SelfCrossMappable[A, Repr](xs: GenTraversableLike[A, Repr]) {
  def crossMap[B, That](f: (A, A) => B)
    (implicit
      cbf: CanBuildFrom[Repr, B, That],
      itl: IsTraversableLike[That] { type A = B }
    ) = xs.flatMap { a => itl.conversion(xs.map(f(a, _)))
  } 
}

implicit def toSelfCrossMappable[Repr](xs: Repr)
  (implicit traversable: IsTraversableLike[Repr]) =
    new SelfCrossMappable(traversable.conversion(xs))

Sample REPL session,

scala> List("foo", "foo", "bar").crossMap(_ == _)
res0: List[Boolean] = List(true, true, false, true, true, false, false, false, true)
like image 108
Miles Sabin Avatar answered Feb 15 '23 11:02

Miles Sabin


In Scala 2.10 you can use implicit classes directly (Miles' answer uses the IsTraversableLike helper which also requires Scala 2.10). It seems that the toSelfCrossMappable (horrible name BTW) is not needed for standard collections. The following works for me:

import collection.generic.{CanBuildFrom, IsTraversableLike}
import collection.GenTraversableLike

implicit class CanCrossMap[A, Repr](xs: GenTraversableLike[A, Repr]) {
  def crossMap[B, That](f: (A, A) => B)(
    implicit cbf: CanBuildFrom[Repr, B, That], 
             itl: IsTraversableLike[That] { type A = B }): That = 
      xs.flatMap { a => itl.conversion(xs.map(f(a, _)))
  } 
}

Another option is to leave out IsTraversableLike completely:

import collection.GenTraversableOnce

implicit class CanCrossMap[A, Repr](xs: GenTraversableLike[A, Repr]) {
  def crossMap[B, That <: GenTraversableOnce[B]](f: (A, A) => B)(
    implicit cbf: CanBuildFrom[Repr, B, That]): That = 
      xs.flatMap { a => xs.map(f(a, _))}
}

Example:

Vector((1.0, 2.0), (3.0, 4.0), (5.0, 6.0)).crossMap { case ((ax, ay), (bx, by)) =>
  val dx = bx - ax
  val dy = by - ay
  math.sqrt(dx*dx + dy*dy)
}

Miles answer covers a few extra cases, where the collection is not directly a GenTraversableLike, namely Array and String (try substituting Vector for Array in the last example).

like image 42
0__ Avatar answered Feb 15 '23 10:02

0__