0

Given the following code :

def flatMap2[A, B, C](s:Seq[(A, B)])(f: B => Seq[C]) : Seq[(A, C)] =
  s.flatMap { case (a, b) => f(b).map((a, _)) }

Is there a better way to code that (maybe something exit in scalaz) ?

Can you help me to find a better name ?

Is there a more generic abstraction to use (Iterable, TraversableOnce) ?

Yann Moisan
  • 8,161
  • 8
  • 47
  • 91

3 Answers3

1

How about this:

import scala.collection.GenTraversable
import scala.collection.GenTraversableLike
import scala.collection.GenTraversableOnce
import scala.collection.generic.CanBuildFrom

implicit class WithMapFlatMapValues[A, B, Repr <: GenTraversable[(A, B)]](val self: GenTraversableLike[(A, B), Repr]) extends AnyVal {
  def flatMapValues[C, That](f: B => GenTraversableOnce[C])(implicit bf: CanBuildFrom[Repr, (A, C), That]) = {
    self.flatMap { case (a, b) => f(b).toIterator.map(a -> _) } 
  }
}

So you can do:

Vector("a"->1, "b"->2).flatMapValues(Seq.fill(_)("x"))
// res1: Vector[(String, String)] = Vector((a,x), (b,x), (b,x))

Set("a"->1, "b"->2).flatMapValues(Iterator.tabulate(_)(_+"x"))
// res2: Set[(String, String)] = Set((a,0x), (b,0x), (b,1x))

And you can do it with Iterator too (so you have all of TraversableOnce covered):

implicit class WithMapFlatMapValuesItr[A, B](val self: Iterator[(A, B)]) extends AnyVal {
  def flatMapValues[C](f: B => GenTraversableOnce[C]) = {
    for { (a, b) <- self; c <- f(b).toIterator } yield (a -> c)
  }
}

val i1 = Iterator("a"->1, "b"->2).flatMapValues(Seq.fill(_)("x"))
// i1: Iterator[(String, String)] = non-empty iterator
i1.toVector
// res6: Vector[(String, String)] = Vector((a,x), (b,x), (b,x))
dhg
  • 52,383
  • 8
  • 123
  • 144
  • Is there a way to have only one impl ? – Yann Moisan Mar 07 '15 at 10:12
  • Why not using only Traversable instead of (GenTraversableOnce, GenTraversableLike) ? – Yann Moisan Mar 07 '15 at 12:25
  • Ex: implicit class WithFlatMapValues[A, B](s: Traversable[(A, B)]) { def flatMapValues[C](f: B => TraversableOnce[C]): Traversable[(A, C)] = s.flatMap { case (a, b) => f(b).map((a, _))} } – Yann Moisan Mar 07 '15 at 12:37
  • Using GenTraversable means it will work on more stuff than Traversable. Using GenTraversableLike/CanBuildFrom makes it work like Scala's built-in collections: producing the same type that it was called on. The little version you wrote will always return an object of type Traversable, and never a more specific type. – dhg Mar 07 '15 at 18:10
0

Looks little better:

def flatMap2[A, B, C](List[(A, B)])(f: B => Seq[C]) : List[(A, C)] =
   for((a,b) <- s; bb <- f(b)) yield a -> bb

But things you're trying to do here is more like about multi-map (with preserving order) than just sequence of tuples:

def flatMap2[A, B, C](s: ListMap[A, List[B]])(f: B => List[C]) : Map[A, List[C]] = 
   s.mapValues(_.flatMap(f))

scala> flatMap2(ListMap(2 -> List("a"), 1 -> List("c")))(x => List(x + "a"))
res4: scala.collection.immutable.Map[Int,List[String]] = Map(2 -> List(aa), 1 -> List(ca))

ListMap saves original order here. mapValues returns view on ListMap, which also will preserve the order.

Scalaz's monoids could help you with adding elelements to such Map:

yourListMap |+| ListMap(key -> List(value))

scala>  val map = ListMap(2 -> List("a"), 1 -> List("c"))
map: scala.collection.immutable.ListMap[Int,List[String]] = Map(2 -> List(a), 1 -> List(c))

scala> (map: Map[Int,List[String]]) |+| ListMap(1 -> List("b"))
res11: scala.collection.immutable.Map[Int,List[String]] = Map(2 -> List(a), 1 -> List(c, b))

You can use Seq instead of List, but then you won't have implicit conversion to the semigroup.

Community
  • 1
  • 1
dk14
  • 22,206
  • 4
  • 51
  • 88
-1

The question may be unclear, at this point I would say:

// s1: Seq[(A, B)], f: B => C
val s2: Seq[(A, C)] = s1.map((a, b) => a -> f(b))
cchantep
  • 9,118
  • 3
  • 30
  • 41
  • 1
    this does not compile; even if modified to compile, it does not implement the desired behavior – dhg Mar 07 '15 at 06:21