Transforming one record into multiple records - scala

If the format of the input is
(x1,(a,b,c,List(key1, key2))
(x2,(a,b,c,List(key3))
and I would like to achieve this output
(key1,(a,b,c,x1))
(key2,(a,b,c,x1))
(key3,(a,b,c,x2))
Here is the code:
var hashtags = joined_d.map(x => (x._1, (x._2._1._1, x._2._2, x._2._1._4, getHashTags(x._2._1._4))))
var hashtags_keys = hashtags.map(x => if(x._2._4.size == 0) (x._1, (x._2._1, x._2._2, x._2._3, 0)) else
x._2._4.map(y => (y, (x._2._1, x._2._2, x._2._3, 1))))
The function getHashTags() returns a list. If the list is not empty, we want to use each elements in the list as the new key. How should i work around this issue?

With rdd created as:
val rdd = sc.parallelize(
Seq(
("x1",("a","b","c",List("key1", "key2"))),
("x2", ("a", "b", "c", List("key3")))
)
)
You can use flatMap like this:
rdd.flatMap{ case (x, (a, b, c, list)) => list.map(k => (k, (a, b, c, x))) }.collect
// res12: Array[(String, (String, String, String, String))] =
// Array((key1,(a,b,c,x1)),
// (key2,(a,b,c,x1)),
// (key3,(a,b,c,x2)))

Here's one way to do it:
val rdd = sc.parallelize(Seq(
("x1", ("a", "b", "c", List("key1", "key2"))),
("x2", ("a", "b", "c", List("key3")))
))
val rdd2 = rdd.flatMap{
case (x, (a, b, c, l)) => l.map( (_, (a, b, c, x) ) )
}
rdd2.collect
// res1: Array[(String, (String, String, String, String))] = Array((key1,(a,b,c,x1)), (key2,(a,b,c,x1)), (key3,(a,b,c,x2)))

Related

Reverse a Map which has A Set as its value using HOF

I am trying to reverse a map that has a String as the key and a set of numbers as its value
My goal is to create a list that contains a tuple of a number and a list of strings that had the same number in the value set
I have this so far:
def flipMap(toFlip: Map[String, Set[Int]]): List[(Int, List[String])] = {
toFlip.flatMap(_._2).map(x => (x, toFlip.keys.toList)).toList
}
but it is only assigning every String to every Int
val map = Map(
"A" -> Set(1,2),
"B" -> Set(2,3)
)
should produce:
List((1, List(A)), (2, List(A, B)), (3, List(B)))
but is producing:
List((1, List(A, B)), (2, List(A, B)), (3, List(A, B)))
This works to, but it's not exactly what you might need and you may need some conversions to get the exact data type you need:
toFlip.foldLeft(Map.empty[Int, Set[String]]) {
case (acc, (key, numbersSet)) =>
numbersSet.foldLeft(acc) {
(updatingMap, newNumber) =>
updatingMap.updatedWith(newNumber) {
case Some(existingSet) => Some(existingSet + key)
case None => Some(Set(key))
}
}
}
I used Set to avoid duplicate key insertions in the the inner List, and used Map for better look up instead of the outer List.
You can do something like this:
def flipMap(toFlip: Map[String, Set[Int]]): List[(Int, List[String])] =
toFlip
.toList
.flatMap {
case (key, values) =>
values.map(value => value -> key)
}.groupMap(_._1)(_._2)
.view
.mapValues(_.distinct)
.toList
Note, I personally would return a Map instead of a List
Or if you have cats in scope.
def flipMap(toFlip: Map[String, Set[Int]]): Map[Int, Set[String]] =
toFlip.view.flatMap {
case (key, values) =>
values.map(value => Map(value -> Set(key)))
}.toList.combineAll
// both scala2 & scala3
scala> map.flatten{ case(k, s) => s.map(v => (k, v)) }.groupMapReduce{ case(k, v) => v }{case(k, v) => List(k)}{ _ ++ _ }
val res0: Map[Int, List[String]] = Map(1 -> List(A), 2 -> List(A, B), 3 -> List(B))
// scala3 only
scala> map.flatten((k, s) => s.map(v => (k, v))).groupMapReduce((k, v) => v)((k, v) => List(k))( _ ++ _ )
val res1: Map[Int, List[String]] = Map(1 -> List(A), 2 -> List(A, B), 3 -> List(B))

scala unpack tuple into case class arguments and additional zip two sequences

I want to transform Seq[String, Seq[Char]] into Seq[UnpackedObject] but don't know how to unpack tuple of two Chars (A, B) to separate case class arguments.
I want to basically create s3 out of s1 and s2 such that:
Seq(("aaa", "A", B"), ("bbb", "B", C"), ("ccc", "C", "D"), ("ddd", "D", "D"))
hence I am trying to use case class but:
problem 1: unpacking tuple into two arguments;
problem 2: last element with "D", "D" <-- I don't know how to solve it.
val s1 = Seq("aaa", "bbb", "ccc", "ddd")
val s2 = ('A' to 'D').sliding(2).toSeq
val pairs = (s1, s2).zipped.map { case (a, b) => UnpackedObject(a, b) }
case class UnpackedObject(a: String, b: Char, c: Char)
this above is my code so far.
zipped function expects Seq with the same length but you passed s2 of length 3 and s1 length is 4. You need to add one element into s2 to get s3:
val s1 = Seq("aaa", "bbb", "ccc", "ddd")
val s2 = ('A' to 'D').sliding(2).toSeq :+ Seq('D', 'D')
// ('A' to 'D').sliding(2) will return just
// Seq(Seq('A', 'B'), Seq('B', 'C'), Seq('C', 'D'))
val pairs = (s1, s2).zipped.map { case (a, b) => (a, b.head, b.last) }
// will return Seq((aaa,A,B), (bbb,B,C), (ccc,C,D), (ddd,D,D))
if you need to create UnpackedObject, you can do it just call tupled apply function of case class:
val objects = (s1, s2).zipped.map { case (a, b) => (a, b.head, b.last) }
.map((UnpackedObject.apply _).tupled)
// will return
// Seq(
// UnpackedObject(aaa,A,B), UnpackedObject(bbb,B,C),
// UnpackedObject(ccc,C,D), UnpackedObject(ddd,D,D))
b is not tuple but rather a indexed sequence, so:
val pairs = (s1, s2).zipped.map { case (a, b) => UnpackedObject(a, b(0), b(1)) }
or
val pairs = (s1, s2).zipped.map { case (a, b) => UnpackedObject(a, b.head, b.last) }
As for 2nd point and using tuples you can do:
val s1 = Seq("aaa", "bbb", "ccc", "ddd")
val s2 = ('A' to 'D').zip(('B' to 'D')) :+ ('D', 'D')
val pairs = (s1, s2).zipped.map { case (a, b) => UnpackedObject(a, b._1, b._2) }

removing the some in left join RDD in spark

I'm running a left join in a Spark RDD but sometimes I get an output like this:
(k, (v, Some(w)))
or
(k, (v, None))
how do I make it so it give me back just
(k, (v, (w)))
or
(k, (v, ()))
here is how I'm combining 2 files..
def formatMap3(
left: String = "", right: String = "")(m: String = "") = {
val items = m.map{k => {
s"$k"}}
s"$left$items$right"
}
val combPrdGrp = custPrdGrp3.leftOuterJoin(cmpgnPrdGrp3)
val combPrdGrp2 = combPrdGrp.groupByKey
val combPrdGrp3 = combPrdGrp2.map { case (n, list) =>
val formattedPairs = list.map { case (a, b) => s"$a $b" }
s"$n ${formattedPairs.mkString}"
}
If you're just interesting in getting formatted output without the Somes/Nones, then something like this should work:
val combPrdGrp3 = combPrdGrp2.map { case (n, list) =>
val formattedPairs = list.map {
case (a, Some(b)) => s"$a $b"
case (a, None) => s"$a, ()"
}
s"$n ${formattedPairs.mkString}"
}
If you have other uses in mind then you probably need to provide more details.
The leftOuterJoin() function in Spark returns the tuples containing the join key, the left set's value and an Option of the right set's value. To extract from the Option class, simply call getOrElse() on the right set's value in the resultant RDD. As an example:
scala> val rdd1 = sc.parallelize(Array(("k1", 4), ("k4", 7), ("k8", 10), ("k6", 1), ("k7", 4)))
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[13] at parallelize at <console>:21
scala> val rdd2 = sc.parallelize(Array(("k5", 4), ("k4", 3), ("k0", 2), ("k6", 5), ("k1", 6)))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[14] at parallelize at <console>:21
scala> val rdd_join = rdd1.leftOuterJoin(rdd2).map { case (a, (b, c: Option[Int])) => (a, (b, (c.getOrElse()))) }
rdd_join: org.apache.spark.rdd.RDD[(String, (Int, AnyVal))] = MapPartitionsRDD[18] at map at <console>:25'
scala> rdd_join.take(5).foreach(println)
...
(k4,(7,3))
(k6,(1,5))
(k7,(4,()))
(k8,(10,()))
(k1,(4,6))

Spark/Scala: Expand a list of (List[String], String) tuples

Basically this question only for Scala.
How can I do the following transformation given an RDD with elements of the form
(List[String], String) => (String, String)
e.g.
([A,B,C], X)
([C,D,E], Y)
to
(A, X)
(B, X)
(C, X)
(C, Y)
(D, Y)
(E, Y)
So
scala> val l = List((List('a, 'b, 'c) -> 'x), List('c, 'd, 'e) -> 'y)
l: List[(List[Symbol], Symbol)] = List((List('a, 'b, 'c),'x),
(List('c, 'd, 'e),'y))
scala> l.flatMap { case (innerList, c) => innerList.map(_ -> c) }
res0: List[(Symbol, Symbol)] = List(('a,'x), ('b,'x), ('c,'x), ('c,'y),
('d,'y), ('e,'y))
With Spark you can solve your problem with:
object App {
def main(args: Array[String]) {
val input = Seq((List("A", "B", "C"), "X"), (List("C", "D", "E"), "Y"))
val conf = new SparkConf().setAppName("Simple Application").setMaster("local[4]")
val sc = new SparkContext(conf)
val rdd = sc.parallelize(input)
val result = rdd.flatMap {
case (list, label) => {
list.map( (_, label))
}
}
result.foreach(println)
}
}
This will output:
(C,Y)
(D,Y)
(A,X)
(B,X)
(E,Y)
(C,X)
I think that the RDD flatMapValues suits this case best.
val A = List((List(A,B,C),X),(List(A,B,C),Y))
val rdd = sc.parallelize(A)
val output = rdd.map(x=>(x._2,x._1)).flatMapValues(x=>x)
which will map X with every value in the List(A,B,C) resulting in RDD of pairs of RDD[(X,A),(X,B),(X,C)...(Y,A),(Y,B),(Y,C)]
val l = (List(1, 2, 3), "A")
val result = l._1.map((_, l._2))
println(result)
Will give you:
List((1,A), (2,A), (3,A))
Using beautiful for comprehensions and making the parameters generic
def convert[F, S](input: (List[F], S)): List[(F, S)] = {
for {
x <- input._1
} yield {
(x, input._2)
}
}
a sample call
convert(List(1, 2, 3), "A")
will give you
List((1,A), (2,A), (3,A))

What's the idiomatic way to map producing 0 or 1 results per entry?

What's the idiomatic way to call map over a collection producing 0 or 1 result per entry?
Suppose I have:
val data = Array("A", "x:y", "d:e")
What I'd like as a result is:
val target = Array(("x", "y"), ("d", "e"))
(drop anything without a colon, split on colon and return tuples)
So in theory I think I want to do something like:
val attempt1 = data.map( arg => {
arg.split(":", 2) match {
case Array(l,r) => (l, r)
case _ => (None, None)
}
}).filter( _._1 != None )
What I'd like to do is avoid the need for the any-case and get rid of the filter.
I could do this by pre-filtering (but then I have to test the regex twice):
val attempt2 = data.filter( arg.contains(":") ).map( arg => {
val Array(l,r) = arg.split(":", 2)
(l,r)
})
Last, I could use Some/None and flatMap...which does get rid of the need to filter, but is it what most scala programmers would expect?
val attempt3 = data.flatMap( arg => {
arg.split(":", 2) match {
case Array(l,r) => Some((l,r))
case _ => None
}
})
It seems to me like there'd be an idiomatic way to do this in Scala, is there?
With a Regex extractor and collect :-)
scala> val R = "(.+):(.+)".r
R: scala.util.matching.Regex = (.+):(.+)
scala> Array("A", "x:y", "d:e") collect {
| case R(a, b) => (a, b)
| }
res0: Array[(String, String)] = Array((x,y), (d,e))
Edit:
If you want a map, you can do:
scala> val x: Map[String, String] = Array("A", "x:y", "d:e").collect { case R(a, b) => (a, b) }.toMap
x: Map[String,String] = Map(x -> y, d -> e)
If performance is a concern, you can use collection.breakOut as shown below to avoid creation of an intermediate array:
scala> val x: Map[String, String] = Array("A", "x:y", "d:e").collect { case R(a, b) => (a, b) } (collection.breakOut)
x: Map[String,String] = Map(x -> y, d -> e)