applying partial function on a tuple field, maintaining the tuple structure - scala

I have a PartialFunction[String,String] and a Map[String,String].
I want to apply the partial functions on the map values and collect the entries for which it was applicaple.
i.e. given:
val m = Map( "a"->"1", "b"->"2" )
val pf : PartialFunction[String,String] = {
case "1" => "11"
}
I'd like to somehow combine _._2 with pfand be able to do this:
val composedPf : PartialFunction[(String,String),(String,String)] = /*someMagicalOperator(_._2,pf)*/
val collected : Map[String,String] = m.collect( composedPf )
// collected should be Map( "a"->"11" )
so far the best I got was this:
val composedPf = new PartialFunction[(String,String),(String,String)]{
override def isDefinedAt(x: (String, String)): Boolean = pf.isDefinedAt(x._2)
override def apply(v1: (String, String)): (String,String) = v1._1 -> pf(v1._2)
}
is there a better way?

Here is the magical operator:
val composedPf: PartialFunction[(String, String), (String, String)] =
{case (k, v) if pf.isDefinedAt(v) => (k, pf(v))}
Another option, without creating a composed function, is this:
m.filter(e => pf.isDefinedAt(e._2)).mapValues(pf)

There is a function in Scalaz, that does exactly that: second
scala> m collect pf.second
res0: scala.collection.immutable.Map[String,String] = Map(a -> 11)
This works, because PartialFunction is an instance of Arrow (a generalized function) typeclass, and second is one of the common operations defined for arrows.

Related

Scala: Collect values defined in hashmap passed by a list argument

Suppose I have the following variables:
val m = HashMap( ("1", "one"), ("2", "two"), ("3", "three") )
val l = List("1", "2")
I would like to extract the list List("one","two"), which corresponds to the values for each key in the list present in the map.
This is my solution, works like a charm. Still I would like to know if I'm reinventing the wheel and if there's some idiomatic solution for doing what I intend to do:
class Mapper[T,V](val map: HashMap[T,V]) extends PartialFunction[T, V]{
override def isDefinedAt(x: T): Boolean = map.contains(x)
override def apply(x: T): V = map.get(x) match {
case Some(v) => v
}
}
val collected = l collect (new Mapper(map) )
List("one", "two")
Yes, you are reinventing the wheel. Your code is equivalent to
l collect m
but with additional layer of indirection that doesn't add anything to HashMap (which already implements PartialFunction—just expand the "Linear Supertypes" list to see that).
Alternatively, you can also use flatMap as follows:
l flatMap m.get
The implicit CanBuildFroms make sure that the result is actually a List.
You could do this, which seems a bit simpler:
val res = l.map(m.get(_)) // List(Some("one"), Some("two"))
.flatMap(_.toList)
Or even this, using a for-comprehension:
val res = for {
key <- l
value <- m.get(key)
} yield value
I would suggest something like this:
m.collect { case (k, v) if l.contains(k) => v }
note:
does not preserve the order from l
does not handle the case of duplicates in l

How to do flatten in scala horizantally?

I am trying some basic logic using scala . I tried the below code but it throws error .
scala> val data = ("HI",List("HELLO","ARE"))
data: (String, List[String]) = (HI,List(HELLO, ARE))
scala> data.flatmap( elem => elem)
<console>:22: error: value flatmap is not a member of (String, List[String])
data.flatmap( elem => elem)
Expected Output :
(HI,HELLO,ARE)
Could some one help me to fix this issue?
You are trying to flatMap over a tuple, which won't work. The following will work:
val data = List(List("HI"),List("HELLO","ARE"))
val a = data.flatMap(x => x)
This will be very trivial in scala:
val data = ("HI",List("HELLO","ARE"))
println( data._1 :: data._2 )
what exact data structure are you working with?
If you are clear about you data structure:
type rec = (String, List[String])
val data : rec = ("HI",List("HELLO","ARE"))
val f = ( v: (String, List[String]) ) => v._1 :: v._2
f(data)
A couple of observations:
Currently there is no flatten method for tuples (unless you use shapeless).
flatMap cannot be directly applied to a list of elements which are a mix of elements and collections.
In your case, you can make element "HI" part of a List:
val data = List(List("HI"), List("HELLO","ARE"))
data.flatMap(identity)
Or, you can define a function to handle your mixed element types accordingly:
val data = List("HI", List("HELLO","ARE"))
def flatten(l: List[Any]): List[Any] = l.flatMap{
case x: List[_] => flatten(x)
case x => List(x)
}
flatten(data)
You are trying to flatMap on Tuple2 which is not available in current api
If you don't want to change your input, you can extract the values from Tuple2 and the extract the values for second tuple value as below
val data = ("HI",List("HELLO","ARE"))
val output = (data._1, data._2(0), data._2(1))
println(output)
If that's what you want:
val data = ("HI",List("HELLO,","ARE").mkString(""))
println(data)
>>(HI,HELLO,ARE)

Scala Map Reduction and Aggregation

I have a Map that looks like this and is of Type Map[String, Seq[String]]
Map(
"5" -> Seq("5.1"),
"5.1" -> Seq("5.1.1", "5.1.2"),
"5.1.1" -> Seq("5.1.1.1"),
"5.1.2" -> Seq.empty[String],
"5.1.1.1" -> Seq.empty[String]
)
Given a key, I would like to fetch all the values recursively that belongs to the given key. Say for example., if I want to look up for the key 5, I expect the result to be:
Given Input is: 5
Expected Output is: Seq(5.1, 5.1.1, 5.1.2, 5.1.1.1)
Here is what I tried so far:
def fetchSequence(inputId: String, acc: Seq[String], seqMap: Map[String, Seq[String]]): Seq[String] = seqMap.get(inputId) match {
case None => acc
case Some(subSeq) =>
val newAcc = acc ++ subSeq
subSeq.collect {
case subId=> fetchSequence(subId, newAcc, seqMap)
}.flatten
}
I get an empty result when I call fetchSequence with the Map that I have above.
Somewhat more concise :
def recGet[A](map: Map[A, Seq[A]])(key: A): Seq[A] =
map.get(key).fold(
// return empty Seq if key not found
Seq.empty[A])(
// return a Seq with
// the key and
// the result of recGet called recursively
// (for all the elements in the Seq[A] found for key)
x => Seq(key) ++ x.flatMap(recGet(map)))
You can use recGet as :
val sections = Map(
"5" -> Seq("5.1"),
"5.1" -> Seq("5.1.1", "5.1.2"),
"5.1.1" -> Seq("5.1.1.1"),
"5.1.2" -> Seq.empty[String],
"5.1.1.1" -> Seq.empty[String]
)
recGet(sections)("5") // Seq[String] = List(5, 5.1, 5.1.1, 5.1.1.1, 5.1.2)
recGet(sections)("5.1.1") // Seq[String] = List(5.1.1, 5.1.1.1)
recGet(sections)("5.2") // Seq[String] = List()
This will also give you the (first) element itself (if it exists in the map), if you don't want that, you can probably wrap recGet in another method which uses drop(1) on the result of recGet.

Combining a filter within a map

I have a list which I am combining to a map in this way, by calling the respective value calculation function. I am using collection.breakout to avoid creating unnecessary intermediate collections since what I am doing is a bit combinatorial, and every little bit of saved iterations helps.
I need to filter out certain tuples from the map, in my case where the value is less than 0. Is it possible to add this to the map itself rather than doing a filter afterwards (thus iterating once again)?
val myMap: Map[Key, Int] = keyList.map(key => key -> computeValue(key))(collection.breakOut)
val myFilteredMap = myMap.filter(_._2 >= 0)
In other words I wish to obtain the second map ideally at one go, so ideally in the first call to map() I filter out the tuples I don't want. Is this possible in any way?
You can easily do this with a foldLeft:
keyList.foldLeft( Map[Key,Int]() ) {
(map, key) =>
val value = computeValue(key)
if ( value >= 0 ) {
map + (key -> value)
} else {
map
}
}
It would probably be best to do a flatMap:
import collection.breakOut
type Key = Int
val keyList = List(-1,0,1,2,3)
def computeValue(i: Int) = i*2
val myMap: Map[Key, Int] =
keyList.flatMap { key =>
val v = computeValue(key)
if (v >= 0) Some(key -> v)
else None
}(breakOut)
You can use collect
val myMap: Map[Key, Int] =
keyList.collect {
case key if computeValue(key) >= 0 => key -> computeValue(key)
}(breakOut)
But that requires re-computing computeValue(key), which is silly. Collect is better when you filter then map.
Or make your own method!:
import scala.collection.generic.CanBuildFrom
import scala.collection.TraversableLike
implicit class EnrichedWithMapfilter[A, Repr](val self: TraversableLike[A, Repr]) extends AnyVal {
def maptofilter[B, That](f: A => B)(p: B => Boolean)(implicit bf: CanBuildFrom[Repr, (A, B), That]): That = {
val b = bf(self.asInstanceOf[Repr])
b.sizeHint(self)
for (x <- self) {
val v = f(x)
if (p(v))
b += x -> f(x)
}
b.result
}
}
val myMap: Map[Key, Int] = keyList.maptofilter(computeValue)(_ >= 0)(breakOut)

How to apply function to each tuple of a multi-dimensional array in Scala?

I've got a two dimensional array and I want to apply a function to each value in the array.
Here's what I'm working with:
scala> val array = Array.tabulate(2,2)((x,y) => (0,0))
array: Array[Array[(Int, Int)]] = Array(Array((0,0), (0,0)), Array((0,0), (0,0)))
I'm using foreach to extract the tuples:
scala> array.foreach(i => i.foreach(j => println(i)))
[Lscala.Tuple2;#11d559a
[Lscala.Tuple2;#11d559a
[Lscala.Tuple2;#df11d5
[Lscala.Tuple2;#df11d5
Let's make a simple function:
//Takes two ints and return a Tuple2. Not sure this is the best approach.
scala> def foo(i: Int, j: Int):Tuple2[Int,Int] = (i+1,j+2)
foo: (i: Int,j: Int)(Int, Int)
This runs, but need to apply to array(if mutable) or return new array.
scala> array.foreach(i => i.foreach(j => foo(j._1, j._2)))
Shouldn't be to bad. I'm missing some basics I think...
(UPDATE: removed the for comprehension code which was not correct - it returned a one dimensional array)
def foo(t: (Int, Int)): (Int, Int) = (t._1 + 1, t._2 + 1)
array.map{_.map{foo}}
To apply to a mutable array
val array = ArrayBuffer.tabulate(2,2)((x,y) => (0,0))
for (sub <- array;
(cell, i) <- sub.zipWithIndex)
sub(i) = foo(cell._1, cell._2)
2dArray.map(_.map(((_: Int).+(1) -> (_: Int).+(1)).tupled))
e.g.
scala> List[List[(Int, Int)]](List((1,3))).map(_.map(((_: Int).+(1) -> (_: Int).+(1)).tupled))
res123: List[List[(Int, Int)]] = List(List((2,4)))
Dot notation required to force