Create map based on condition from Future of List in Scala - scala

I have method with param type Future[List[MyRes]]. MyRes has two option fields id and name. Now I want to create map of id and name if both present. I am able to create map with default value as follow but I don't want to have default value just skip the entry with null value on either.
def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
myRes.map (
_.map(
o =>
(o.id match {
case Some(id) => id.toLong
case _ => 0L
}) ->
(o.name match {
case Some(name) => name
case _ => ""
})
).toMap)
Any suggestion?

You are looking for collect :)
myRes.map {
_.iterator
.map { r => r.id -> r.name }
.collect { case(Some(id), Some(name) => id -> name }
.toMap
}
If your MyRes thingy is a case class, then you don't need the first .map:
myRes.map {
_.collect { case MyRes(Some(id), Some(name)) => id -> name }
.toMap
}
collect is like .map, but it takes a PartialFunction, and skips over elements on which it is not defined. It is kinda like your match statement but without the defaults.
Update:
If I am reading your comment correctly, and you want to log a message when either field is a None, collect won't help with that, but you can do flatMap:
myRes.map {
_.flatMap {
case MyRes(Some(id), Some(name)) => Some(id -> name)
case x => loger.warn(s"Missing fields in $x."); None
}
.toMap
}

Try this:
def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
myRes.map (
_.flatMap(o =>
(for (id <- o.id; name <- o.name) yield (id.toLong -> name)).toList
).toMap
)
}
The trick is flattening List[Option[(Long,String)]] by using flatMap and converting the Option to a List.

Related

Scala: Best way to remove tuples from Seq where one value is None

I wish to filter out None values where they appear in a Seq of tuples.
In the code below, I want to replace getOrElse with get. But then how do I remove the tuples where the first value is None ?
Here is my code. I feel it is inelegant.
myFirstMap.map {
case (key, value) =>
val tuple = (myLookUpMap.getOrElse(key,MyCaseClass("", None)), value.toString)
tuple
}.filter(_._1.name.nonEmpty).toIndexedSeq
}
What is the correct way to do this?
NOTE: this method will be called thousands of times on Seq with length 40 to 100, so performance is important
It looks like .map() and .flatMap() should do the trick, which is what a for comprehension is all about.
(for {
(k, v) <- myFirstMap
mcc <- myLookUpMap.get(k)
} yield (mcc, v.toString)).toIndexedSeq
myFirstMap.collect { case (k, _) if myFilterMap.contains(k) => myFilterMap(k)}
Maybe
myFirstMap.map {
case (key, value) =>
myLookUpMap.get(key).map( found => Tuple2( found, value.toString ) )
}.withFilter(_.nonEmpty).map( _.get ).toIndexedSeq
...or more readably...
val mbTuples = myFirstMap.map {
case (key, value) =>
myLookUpMap.get(key).map( found => Tuple2( found, value.toString ) )
}
val foundTuples = mbTuples.withFilter(_.nonEmpty).map( _.get )
val tupleSeq = foundTuples.toIndexedSeq
Or how about this approach:
val commonKeys = myFirstMap.keySet().intersect( myLookUpMap.keySet() )
val tupleSeq = commonKeys.map { case ( key, value ) =>
( myLookUpMap(key), value.toString )
}.toIndexedSeq
You can use flatMap to filter None in collection
myFirstMap.flatMap { case (key, value) => myLookUpMap.get(key).map(entity => (entity, value.toString)) }

Scala return variable type after future is complete Add Comment Collapse

I've got a problem with returning a list after handling futures in scala. My code looks like this:
def getElements(arrayOfIds: Future[Seq[Int]]): Future[Seq[Element]] = {
var elementArray: Seq[Element] = Seq()
arrayOfIds.map {
ids => ids.map(id => dto.getElementById(id).map {
case Some(element) => elementArray = elementArray :+ element
case None => println("Element not found")
})
}
arrayOfIds.onComplete(_ => elementArray)
}
I'd like to do something like .onComplete, however the return type is
Unit and I'd like to return a Future[Seq[Whatever]]. Is there clean way to handle futures like this? Thanks!
Please provide the type of function dto.getElementById. If it is Int => Future[Option[Element]], then:
def getElements(arrayOfIds: Future[Seq[Int]]): Future[Seq[Element]] = {
val allElements: Future[Seq[Option[Element]]] = arrayOfIds.flatMap( ids =>
Future.sequence(ids.map(dto.getElementById))
)
allElements.map(_.flatMap{
case None => println();None
case some => some
})
}
Without logging, it would be:
arrayOfIds.flatMap( ids => Future.traverse(ids.map(dto.getElementById))(_.flatten))
Instead of assigning the result to a mutable variable, return it from the continuation of the Future. You can use flatMap to extract only the Element results which actually contain a value:
def getElements(arrayOfIds: Future[Seq[Int]]): Future[Seq[Element]] = {
arrayOfIds.flatMap(id => Future.fold(id.map(getElementById))(Seq.empty[Element])(_ ++ _))
}

Scala map validation

My program receives a scala map, the requirements is to validate this map (key-value pairs). Ex: validate a key value, convert its type from string to int etc. In a rare case, we update the key as well before passing the map to the down layer.
Its not always required to update this map , but only when we detect that there are any unsupported keys or values.
I'm doing some thing like this:
private def updateMap ( parameters: Map[String, String]): Map[String, String] = {
parameters.map{
case(k,v) => k match { case "checkPool" =>
(k, (if (k.contains("checkPool"))
v match {
case "1" => "true"
case _ => "false"
}
else v))
case "Newheader" => (k.replace("Newheader","header"),v)
case _ =>(k,v)
}
case _ => ("","")
}
}
Like this the code increases for doing the validation and converting the keys/values to supported ones.
Is there a cleaner way of doing this validation in Scala for a map?
Regards
According to what I understood from your question, match case can be your solution
inOptions.map(kv => kv.keySet.contains(STR) match {
case true => mutable.HashMap(STR_UPDT->kv.get(STR).get)
case _ => kv
})
Edited
Since you updated your question with more requirements, simple if else condition matching seems to be the best choice.
def updateMap(parameters: Map[String, String]): Map[String, String] = {
parameters.map(kv => {
var key = kv._1
var value = kv._2
if(key.contains("checkPool")){
value = if(value.equals("1")) "true" else "false"
}
else if(key.contains("Newheader")){
key = key.replace("Newheader", "header")
}
(key, value)
})
}
You can add more else if conditions

flattening future of option after mapping with a function that return future of option

I have a collection of type Future[Option[String]] and I map it to a function that returns Future[Option[Profile]], but this create a return type of Future[Option[Future[Option[Profile]]]] because queryProfile return type is `Future[Option[Profile]]'
val users: Future[Option[User]] = someQuery
val email: Future[Option[String]] = users map(opSL => opSL map(_.email) )
val userProfile = email map {opE => opE map {E => queryProfile(E)}}
I need to use the Profile object contained deep inside val userProfile without unpacking all these levels, what would be the right way to use flatMap or `flatten', or is there a better approach all together ?
You can get a "partial Future" with something like this:
val maybeProfile: Future[Profile] = users
.collect { case Some(u) => u.email }
.flatMap { email => queryProfile(email) }
.collect { case Some(p) => p }
Now maybeProfile contains the (completely "naked"/unwrapped) Profile instance, but only if it was able to find it. You can .map it as usual to do something else with it, that'll work in the usual ways.
If you want to ever block and wait for completion, you will have to handle the missing case at some point. For example:
val optionalProfile: Option[Profile] = Await.result(
maybeProfile
.map { p => Some(p) } // Or just skip the last `collect` above
.recover { case _:NoSuchElementException => None },
1 seconds
)
If you are happy with just having Future[Option[Profile]], and would prefer to have the "unwrapping" magic, and handling the missing case localized in one place, you can put the two fragments from above together like this:
val maybeProfile: Future[Option[Profile]] = users
.collect { case Some(u) => u.email }
.flatMap { email => queryProfile(email) }
.recover { case _:NoSuchElementException => None }
Or use Option.fold like the other answer suggested:
val maybeProfile: Future[Option[Profile]] = users
.map { _.map(_.email) }
.flatMap { _.fold[Future[Option[Profile]]](Future.successful(None))(queryProfile) }
Personally, I find the last option less readable though.
Personally I think a monad transformer such as OptionT provided by scalaz/cats would be the cleanest approach:
val users = OptionT[Future,User](someQuery)
def queryProfile(email:String) : OptionT[Future,Profile] = ...
for {
u <- users
p <- queryProfile(u.email)
} yield p
I'd just create a helper method like this:
private def resolveProfile(optEmail: Option[String]): Future[Option[Profile] =
optEmail.fold(Future.successful(None)) { email =>
queryProfile(email).map(Some(_))
}
which then allows you to just flatMap your original email future like so:
val userProfile = email.flatMap(resolveProfile)

Tuple seen as Product, compiler rejects reference to element

Constructing phoneVector:
val phoneVector = (
for (i <- 1 until 20) yield {
val p = killNS(r.get("Phone %d - Value" format(i)))
val t = killNS(r.get("Phone %d - Type" format(i)))
if (p == None) None
else
if (t == None) (p,"Main") else (p,t)
}
).filter(_ != None)
Consider this very simple snippet:
for (pTuple <- phoneVector) {
println(pTuple.getClass.getName)
println(pTuple)
//val pKey = pTuple._1.replaceAll("[^\\d]","")
associate() // stub prints "associate"
}
When I run it, I see output like this:
scala.Tuple2
((609) 954-3815,Mobile)
associate
When I uncomment the line with replaceAll(), compile fails:
....scala:57: value _1 is not a member of Product with Serializable
[error] val pKey = pTuple._1.replaceAll("[^\\d]","")
[error] ^
Why does it not recognize pTuple as a Tuple2 and treat it only as Product
OK, this compiles and produces the desired result. But it's too verbose. Can someone please demonstrate a more concise solution for dealing with this typesafe stuff?
for (pTuple <- phoneVector) {
println(pTuple.getClass.getName)
println(pTuple)
val pPhone = pTuple match {
case t:Tuple2[_,_] => t._1
case _ => None
}
val pKey = pPhone match {
case s:String => s.replaceAll("[^\\d]","")
case _ => None
}
println(pKey)
associate()
}
You can do:
for (pTuple <- phoneVector) {
val pPhone = pTuple match {
case (key, value) => key
case _ => None
}
val pKey = pPhone match {
case s:String => s.replaceAll("[^\\d]","")
case _ => None
}
println(pKey)
associate()
}
Or simply phoneVector.map(_._1.replaceAll("[^\\d]",""))
By changing the construction of phoneVector, as wrick's question implied, I've been able to eliminate the match/case stuff because Tuple is assured. Not thrilled by it, but Change is Hard, and Scala seems cool.
Now, it's still possible to slip a None value into either of the Tuple values. My match/case does not check for that, and I suspect that could lead to a runtime error in the replaceAll call. How is that allowed?
def killNS (s:Option[_]) = {
(s match {
case _:Some[_] => s.get
case _ => None
}) match {
case None => None
case "" => None
case s => s
}
}
val phoneVector = (
for (i <- 1 until 20) yield {
val p = killNS(r.get("Phone %d - Value" format(i)))
val t = killNS(r.get("Phone %d - Type" format(i)))
if (t == None) (p,"Main") else (p,t)
}
).filter(_._1 != None)
println(phoneVector)
println(name)
println
// Create the Neo4j nodes:
for (pTuple <- phoneVector) {
val pPhone = pTuple._1 match { case p:String => p }
val pType = pTuple._2
val pKey = pPhone.replaceAll(",.*","").replaceAll("[^\\d]","")
associate(Map("target"->Map("label"->"Phone","key"->pKey,
"dial"->pPhone),
"relation"->Map("label"->"IS_AT","key"->pType),
"source"->Map("label"->"Person","name"->name)
)
)
}
}