Scala flatten a Seq[Future[Seq[]]] - scala

I'm stuck trying to flatten a Seq[Seq[]] with returning the outcome. So what I had was this:
def getListsByLC(lcId: Int): Action[AnyContent] = Action.async {
listRepo.getListsByLC(lcId).flatMap { lists =>
val items: Seq[Future[Seq[Item]]] = lists.map { list =>
itemRepo.getItemsByList(list.id)
}
Future.sequence(items).map { result =>
Ok(Json.obj("lists" -> lists, "items" -> result))
}
}
}
The outcome was obviously an array of arrays
Now what I wanted to do is flatten this Future.sequence in order to only have one array containing all items. This, alongside similar versions I found browsing the web, is what I tried:
def getListsByLC(lcId: Int): Action[AnyContent] = Action.async {
listRepo.getListsByLC(lcId).flatMap { lists =>
val items: Seq[Future[Seq[Item]]] = lists.map { list =>
itemRepo.getItemsByList(list.id)
}
Future.sequence(items).map(._flatten) { result =>
Ok(Json.obj("lists" -> lists, "items" -> result))
}
}
}
Here I get this compiling error:
Sorry for the quality of the screenshot. Any ideas? Thanks in advance!

You need to call _.flatten instead of ._flatten and you're missing an additional .map call after flattening the sequences:
Future.sequence(items).map(_.flatten).map { result =>
Ok(Json.obj("lists" -> lists, "items" -> result))
}

Related

Adding Futures to an immutable Seq and returning it

I feel like this is not that difficult but I'm struggling with the futures and adding objects or Ints to an immutable Seq.
def createCopyOfProcessTemplate(processTemplateId: Int): Action[AnyContent] = Action.async {
//val copies = Seq()
processTemplateDTO.createCopyOfProcessTemplate(processTemplateId).flatMap { process =>
processTemplateDTO.getProcessStepTemplates(processTemplateId).map { steps =>
steps.foreach(processStep =>
copy: Future[Option[ProcessTemplateModel] = processTemplateDTO.createCopyOfStepTemplates(processTemplateId, process.get.id.get, processStep))
//Seq should look something like this: [{processStep.id, copy.id},{processStep.id, copy.id},...] or [[processStep.id, copy.id],[processStep.id, copy.id],...]
}
Ok(Json.obj("copies" -> copies))
}
Where do I have to define the seq and how should I return it since it's handling Futures ?
Any ideas? Thanks in advance!
You can use Future.sequence to convert List[Future[A]] into Future[List[A]] and return as result. First, do not use steps.foreach with copy variable defined, instead use steps.map to get ProcessTemplateModel as result from processTemplateDTO.createCopyOfStepTemplates, and map will return List of future result - List[Future[Option[ProcessTemplateModel]]. Then you can convert the result with Future.sequence and finally return as Json object.
val copies:List[Future[Option[ProcessTemplateModel]] = processTemplateDTO.createCopyOfProcessTemplate(processTemplateId).flatMap {
process =>processTemplateDTO.getProcessStepTemplates(processTemplateId).map { steps =>
steps.map(processStep =>
processTemplateDTO.createCopyOfStepTemplates(processTemplateId, process.get.id.get, processStep))
}
Future.sequence(copies).map{ result =>
Ok(Json.obj("copies" -> result))
}

Play framework Scala project: add ints to a Seq and return it

How is it possible to iterate over a Future[Seq[]] and add Int values to a Seq and return this Seq?
My code looks like this:
def createCopyOfProcessTemplate(processTemplateId: Int): Action[AnyContent] = Action.async {
processTemplateDTO.createCopyOfProcessTemplate(processTemplateId).map { process =>
processTemplateDTO.getProcessStepTemplates(processTemplateId).map { steps =>
steps.foreach(processStep =>
processTemplateDTO.createCopyOfStepTemplates(processTemplateId, process.get.id.get, processStep))
}
// Fetch all Steps which belongs to this Template
val processSteps: Future[Seq[ProcessStepTemplatesModel]] = processTemplateDTO.getProcessStepTemplates(processTemplateId)
val stepIds:Seq[Int]=Seq()
processSteps.map(
steps => steps.foreach(
step => stepIds:+step.id
)
)
Ok(Json.obj("process" -> process, "steps" -> stepIds))
}
}
Because of asynchronicity, I think, the Seq is empty. How can I solve this?
You could take a different approach:
Action.async {
...
processSteps.map(_.map(_.id))
.map(ids => Ok(Json.obj("process" -> process, "steps" -> ids)))
}

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 how to build an associative List based on a list of Tuples

I am trying to solve a problem: Write a function that will output the largest item association given an input of item association pairs.
Example input:
[[Item1, Item2],
[Item2, Item3],
[Item2, Item4],
[Item5, Item6]]
Output: [Item1, Item2, Item3, Item4]
I'm trying to solve it in a clean and functional style using Scala, this is my initial implementation:
object Main {
def main(args: Array[String]) {
val m = List[(String,String)]("item1" -> "item2",
"item2" -> "item3",
"item2" -> "item4",
"item5" -> "item6")
val l: List[String] = getAssociationList(m)
l.foreach((s) => println(s))
}
def getAssociationList(l:List[(String,String)]) : List[String] = {
def f(k:String, l:List[(String,String)]) : List[String] = {
var list:List[String] = List(k)
l.filter((t:(String,String)) => t._1.equals(k)).foreach(
{case (key, value) => list = list ++ f(value, l)}
)
return list
}
f(l(0)._1, l)
}
}
It outputs the correct result but
I'm using var list:List[String] = List(k) which clearly seems non-functional, is there a way to improve this function in order to make it more cleaner and better aligned with functional style best practices in Scala?
I think this is a more concise and functional way of doing the same thing.
def getAssociationList(l:List[(String,String)]) : List[String] = {
def f(k:String) : List[String] =
k :: l.filter(_._1 == k).flatMap(x => f(x._2))
f(l.head._1)
}

mongodb casbah and list handling

I am having problems writing this function, which takes a string and returns a list of strings associated to it.
(I'm expecting entries like {_id: ...., hash: "abcde", n: ["a","b","ijojoij"]} in mongodb)
def findByHash(hash: Hash) = {
val dbobj = mongoColl.findOne(MongoDBObject("hash" -> hash.hashStr))
val n = dbobj match {
case Some(doc: com.mongodb.casbah.Imports.DBObject) => {
doc("n") match {
case Some(n: com.mongodb.casbah.Imports.DBObject) => {
Some(List[String]() ++ n map { x => x.asInstanceOf[String] })
}
case _ => {
None // hash match but no n in object
}
}
}
case _ => {
None // no hash match
}
}
n
}
Is there anything wrong with the code? Do you know how to correct it?
doc("n") returns AnyRef, so you should explicitly cast it to BasicDBList.
val n = doc("n").asInstanceOf[BasicDBList]
Some(List[String]() ++ n map { x => x.asInstanceOf[String] })