In a previous SO question, I got advice on using Scala Futures with PlayFramework, thank you. Now things have gotten a bit more complicated. Let's say that before I just had to map where fruit could be found:
def getMapData(coll: MongoCollection[Document], s: String): Future[Seq[Document]] = ...
def mapFruit(collection: MongoCollection[Document]) = Action.async {
val fut = getMapData(collection, "fruit")
fut.map { docs: Seq[Document] =>
Ok(docs.toJson)
} recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
}
It turns out that people care more about Apples than Bananas or Cherries, so if no more than 100 items should appear on the map, people want Apples to have priority over Bananas and Cherries, but not more than some percentage of items on a map should be Apples. Some function pickDocs determines the proper mix. I thought something like this might just work, but no:
def mapApplesBananasCherries(collection: MongoCollection[Document]) = Action.async {
val futA = getMapData(collection, "apples")
val futB = getMapData(collection, "bananas")
val futC = getMapData(collection, "cherries")
futA.map { docsA: Seq[Document] =>
futB.map { docsB: Seq[Document] =>
futC.map { docsC: Seq[Document] =>
val docsPicked = pickDocs(100, docsA, docsB, docsC)
Ok(docsPicked.toJson)
}
}
// won't compile without something here, e.g. Ok("whatever")
} recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
}
Life was simple when I just had one Future, but now I have three. What can I do to make this to (1) work and (2) again be simple? I can't really construct a web response until all three Futures have values.
Basically, you should use flatMap
futA.flatMap { docsA: Seq[String] =>
futB.flatMap { docsB: Seq[String] =>
futC.map { docsC: Seq[String] =>
docsPicked = pickDocs(100, docsA, docsB, docsC)
Ok(docsPicked.toJson)
}
}
}
Also, you can use for comprehension:
val res = for {
docsA <- futA
docsB <- futB
docsC <- futC
} yield Ok(pickDocs(100, docsA, docsB, docsC).toJson)
res.recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
If my understanding is that you want to execute apples, cherries and bananas in that priority, I would code it similar to this
import scala.concurrent.{Await, Future}
import scala.util.Random
import scala.concurrent.duration._
object WaitingFutures extends App {
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
val apples = Future {50 + Random.nextInt(100)}
val cherries = Future {50 + Random.nextInt(100)}
val bananas = Future {50 + Random.nextInt(100)}
val mix = for {
app <- apples
cher <- if (app < 100) cherries else Future {0}
ban <- if (app + cher < 100) bananas else Future {0}
} yield (app,cher,ban)
mix.onComplete {m =>
println(s"mix ${m.get}")
}
Await.result(mix, 3 seconds)
}
if apples returns more than 100 when the future completes, it doesn't wait until cherries or bananas are done, but returns a dummy future with 0. If it's not enough it will wait until cherries are executed and so on.
NB I didn't put much effort on how to signal the if, so I'm using the dummy future which might not be the best approach.
This doesn't compile because your nested future block is returning a Future[Future[Future[Response]]]. If you instead use flatMap on the futures, Your futures will not be nested.
If you want this to be a little less repetitive, you can use Future.sequence instead to kick off futures simultaneously. You can either use pattern matching to re-extract the lists:
val futureCollections = List("apples", "bananas", "cherries").map{ getMapData(collection, _) }
Future.sequence(futureCollections) map { case docsA :: docsB :: docsC :: Nil =>
Ok(pickDocs(100, docsA, docsB, docsC).toJson)
} recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
or you could just hand the pickDocs function a list of lists (sorted by priority) for it to pick from.
Future.sequence(futureCollections) map { docLists =>
Ok(pickDocs(docLists, 100, 0.75f).toJson)
} recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
This pickDocs implementation will take a percentage of the head of the list, unless there aren't enough documents in the full list, in which it takes more, then recursively apply the same percentage on the remaining slots lists.
def pickDocs[T](lists: List[List[T]], max: Int, dampPercentage: Float): List[T] = {
lists match {
case Nil => Nil
case head :: tail =>
val remainingLength = tail.flatten.length
val x = max - remainingLength
val y = math.ceil(max * dampPercentage).toInt
val fromHere = head.take(x max y)
fromHere ++ pickDocs(tail, max - fromHere.length, dampPercentage)
}
}
This is a very common pattern for Futures and similar classes that "contain values" (e.g. Option, List)
To combine the results you want to use the flatMap method and the resulting code is
def mapApplesBananasCherries(collection: MongoCollection[Document]) = Action.async {
val futA = getMapData(collection, "apples")
val futB = getMapData(collection, "bananas")
val futC = getMapData(collection, "cherries")
futA.flatMap { docsA =>
futB.flatMap { docsB =>
futC.map { docsC =>
val docsPicked = pickDocs(100, docsA, docsB, docsC)
Ok(docsPicked.toJson)
}
}
} recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
}
In fact it's so common that a special syntax exists to make it more readable, called for-comprehension: the following code is equivalent to the previous snippet
def mapApplesBananasCherries(collection: MongoCollection[Document]) = Action.async {
val futA = getMapData(collection, "apples")
val futB = getMapData(collection, "bananas")
val futC = getMapData(collection, "cherries")
for {
apples <- futA
bananas <- futB
cherries <- futC
} yield {
val docsPicked = pickDocs(100, apples, bananas, cherries)
Ok(docsPicked.toJson)
} recover {
case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}
}
Related
Hey guys i am new in slick, how can I flatten this sequence of sequence? so that can return the commented code
def insertIfNotExists(mapCountryStates: Map[String, Iterable[StateUtil]]): Future[Seq[Seq[StateTable]]] /*: Future[Seq[StateTable]]*/ = {
val interaction = DBIO.sequence(mapCountryStates.toSeq.map { case (alpha2Country, statesUtil) =>
val codes = statesUtil.map(_.alpha3Code)
for {
countryId <- Countries.filter(_.alpha2Code === alpha2Country).map(_.id).result.head
existing <- States.filter(s => (s.alpha3Code inSet codes) && s.countryId === countryId).result
stateTables = statesUtil.map(x => StateTable(0L, x.name, x.alpha3Code, countryId))
statesInserted <- StatesInsertQuery ++= stateTables.filter(s => !existing.exists(x => x.alpha3Code == s.alpha3Code && x.countryId == s.countryId))
} yield existing ++ statesInserted
})
db.run(interaction.transactionally)
}
if I write it here:
val interaction = DBIO.sequence(...).flatten
or here:
db.run(interaction.flatten.transactionally)
[error] Cannot prove that Seq[Seq[StateRepository.this.StateTableMapping#TableElementType]] <:< slick.dbio.DBIOAction[R2,S2,E2].
but when the application runs, because the IDE does not detect it as an error:
I update my definition with DBIO.fold:
It looks like you might be after DBIO.fold. This provides a way to take a number of actions and reduce them down to a single value. In this case, your single value is a Seq[StateTable] from a Seq[Seq[StateTable]].
A sketch of how this could look might be...
def insertIfNotExists(...): DBIO[Seq[StateTable]] = {
val interaction: Seq[DBIO[Seq[StateTable]]] = ...
val startingPoint: Seq[StateTable] = Seq.empty
DBIO.fold(interaction, startingPoint) {
(total, list) => total ++ list
}
}
It looks like the types will line up using fold. Hope it's of some use in your case.
There's some more information about fold in Chapter 4 of Essential Slick.
A viable solution should be to flatten the sequence once the Future has been completed:
def insertIfNotExists(mapCountryStates: Map[String, Iterable[StateUtil]]): Future[Seq[StateTable]] = {
val interaction = DBIO.sequence(mapCountryStates.toSeq.map { case (alpha2Country, statesUtil) =>
val codes = statesUtil.map(_.alpha3Code)
for {
countryId <- Countries.filter(_.alpha2Code === alpha2Country).map(_.id).result.head
existing <- States.filter(s => (s.alpha3Code inSet codes) && s.countryId === countryId).result
stateTables = statesUtil.map(x => StateTable(0L, x.name, x.alpha3Code, countryId))
statesInserted <- StatesInsertQuery ++= stateTables.filter(s => !existing.exists(x => x.alpha3Code == s.alpha3Code && x.countryId == s.countryId))
} yield existing ++ statesInserted
})
db.run(interaction.transactionally).map(_.flatten)
}
val date2 = Option(LocalDate.parse("2017-02-01"))
//date1.compareTo(date2)>=0
case class dummy(val prop:Seq[Test])
case class Test(val s :String)
case class Result(val s :String)
val s = "11,22,33"
val t = Test(s)
val dt =Test("2017-02-06")
val list = dummy(Seq(t))
val list2 = dummy(Seq(dt))
val code = Option("22")
val f = date2.flatMap(c => list2
.prop
.find(d=>LocalDate.parse(d.s)
.compareTo(c)>=0))
.map(_ => Result("Found"))
.getOrElse(Result("Not Found"))
code.flatMap(c => list
.prop
.find(_.s.split(",").contains(c)))
.map(_ => Result("Found"))
.getOrElse(Result("Not Found"))
I want to && the conditions below and return Result("Found")/Result("Not Found")
d=>LocalDate.parse(d.s).compareTo(c)>=0)
_.s.split(",").contains(c)
Is there any possible way to achieve the above .In actual scenerio list and list 2 are Future
I tried to make a more realistic example based on Futures. Here is how I would do it:
val date2 = Option(LocalDate.parse("2017-02-01"))
case class Test(s: String)
case class Result(s: String)
val t = Test("11,22,33")
val dt = Test("2017-02-06")
val code = Option("22")
val f1 = Future(Seq(t))
val f2 = Future(Seq(dt))
// Wait for both futures to finish
val futureResult = Future.sequence(Seq(f1, f2)).map {
case Seq(s1, s2) =>
// Check the first part, this will be a Boolean
val firstPart = code.nonEmpty && s1.exists(_.s.split(",").contains(code.get))
// Check the second part, also a boolean
val secondPart = date2.nonEmpty && s2.exists(d => LocalDate.parse(d.s).compareTo(date2.get) >= 0)
// Do the AND logic you wanted
if (firstPart && secondPart) {
Result("Found")
} else {
Result("Not Found")
}
}
// This is just for testing to see we got the correct result
val result = Await.result(futureResult, Duration.Inf)
println(result)
As an aside, your code and date2 values in your example are Options... If this is true in your production code, then we should do a check first to see if they are both defined. If they are not then there would be no need to continue with the rest of the code:
val futureResult = if (date2.isEmpty || code.isEmpty) {
Future.successful(Result("Not Found"))
} else {
Future.sequence(Seq(f1, f2)).map {
case Seq(s1, s2) =>
val firstPart = s1.exists(_.s.split(",").contains(code.get))
val secondPart = s2.exists(d => LocalDate.parse(d.s).compareTo(date2.get) >= 0)
if (firstPart && secondPart) {
Result("Found")
} else {
Result("Not Found")
}
}
}
Use pattern matching on Option instead of using flatMap
e.g.
val x = Some("20")
x match {
case Some(i) => println(i) //do whatever you want to do with the value. And then return result
case None => Result("Not Found")
}
Looking at what you are trying to do, You would have to use pattern matching twice, that too nested one.
I'm trying to walk through two arrays of potentially different sizes and compose a new array of randomly selected elements from them (for crossover in a genetic algorithm) (childGeneCount is just the length of the longer array).
In the following code snippet, each gene.toString logs, but my code doesn't seem to execute the last log. What dumb thing am I doing?
val genes = for (i <- 0 to childGeneCount) yield {
val gene = if (Random.nextBoolean()) {
if (i < p1genes.length) {
p1genes(i)
} else {
p2genes(i)
}
} else {
if (i < p2genes.length) {
p2genes(i)
} else {
p1genes(i)
}
}
Logger.debug(gene.toString)
gene
}
Logger.debug("crossover finishing - never gets here??")
New to scala, and would be happy for a slap on the wrist accompanied by a "do it this completely different way instead" if appropriate.
You are right, the problem was with "to" should have been "until". I have changed your code a bit to make it more scala like.
val p1genes = "AGTCTC"
val p2genes = "ATG"
val genePair = p1genes.zipAll(p2genes, None, None)
val matchedGene = for (pair <- genePair) yield {
pair match {
case (p1Gene, None) => p1Gene
case (None, p2Gene) => p2Gene
case (p1Gene, p2Gene) => if (Random.nextBoolean()) p1Gene else p2Gene
}
}
println(matchedGene)
The process is:
First zip two dna sequences into one.
Fill the shorter sequence with None.
Now loop over the zipped sequences and populate the new sequence.
Reworked Tawkir's answer, with cleaner None handling:
val p1genes = "AGTCTC"
val p2genes = "ATG"
val genePair = p1genes.map(Some.apply).zipAll(p2genes.map(Some.apply), None, None)
val matchedGene = genePair.map {
case (Some(p1Gene), None) => p1Gene
case (None, Some(p2Gene)) => p2Gene
case (Some(p1Gene), Some(p2Gene)) => if (Random.nextBoolean()) p1Gene else p2Gene
}
println(matchedGene)
If you want to avoid wrapping the sequence with Some, another solution is to use a character known not to appear in the sequence as a "none" marker:
val p1genes = "AGTCTC"
val p2genes = "ATG"
val none = '-'
val genePair = p1genes.zipAll(p2genes, none, none)
val matchedGene = genePair.map {
case (p1Gene, `none`) => p1Gene
case (`none`, p2Gene) => p2Gene
case (p1Gene, p2Gene) => if (Random.nextBoolean()) p1Gene else p2Gene
}
println(matchedGene)
Pretty sure harry0000's answer is correct: I was using "to" like "until", and am so used to exceptions being thrown loudly that I didn't think to look there!
I ended up switching from for/yield to List.tabulate(childGeneCount){ i => {, which fixed the error probably for the same reason.
Since you asked for possible style improvements, here are two suggested implementations. The first one is less idiomatic, but more performant. The second one is prettier but does some more work.
def crossover[E : ClassTag](a: Array[E], b: Array[E]): Array[E] = {
val (larger, smaller) = if(a.length > b.length) (a, b) else (b, a)
val result = Array.ofDim[E](larger.length)
for(i <- smaller.indices)
result(i) = if(Random.nextBoolean()) larger(i) else smaller(i)
for(i <- smaller.length until larger.length)
result(i) = larger(i)
result
}
def crossoverIdiomatic[E : ClassTag](a: Array[E], b: Array[E]): Array[E] = {
val randomPart = (a zip b).map { case (x,y) => if(Random.nextBoolean()) x else y }
val (larger, smaller) = if(a.length > b.length) (a, b) else (b, a)
randomPart ++ larger.drop(smaller.length)
}
val a = Array("1", "2", "3", "4", "5", "6")
val b = Array("one", "two", "three", "four")
// e.g. output: [one,2,three,4,5,6]
println(crossover(a, b).mkString("[", ",", "]"))
println(crossoverIdiomatic(a, b).mkString("[", ",", "]"))
Note that the E : ClassTag are only there to make the compiler happy about using Array[E], if you only need Int for your work, you can drop all the fancy generics.
I'm a bit new to future composition so I haven't figured out all the common patterns yet.
I have a list of futures but I need to associate a name with the futures when they are created so I can somehow reconcile the list.
EG if I create a list of futures like this, how can I get x to be associated with the future's result?
val requestsForMaster = shardNames.map { x ⇒
sentinel ? Request("SENTINEL", "get-master-addr-by-name", x)
}
I would do something like this to get the futures into a sequence
val mastersConfig = Future.sequence(requestsForMaster)
mastersConfig.onSuccess {
case x: List[Some[List[Some[ByteString]]]] ⇒
self ! x.map {
case Some(List(Some(host: ByteString), Some(port: ByteString))) ⇒
println("Host/port: " + host.utf8String + ":" + port.utf8String)
Shard("name", host.utf8String, port.utf8String.toInt, None)
}
}
But when I go to create the Shard object, the name (x) isn't available and I need it in there.
Any idea as to how I can compose these to get the name in there?
Edit:
Here is the solution I used:
val requestsForMaster = shardNames.map { x ⇒
(sentinel ? Request("SENTINEL", "get-master-addr-by-name", x)).map(y ⇒ (x, y))
}
val mastersConfig = Future.sequence(requestsForMaster)
mastersConfig.onSuccess {
case x: (List[(String, Some[List[Some[ByteString]]])]) ⇒
self ! x.map {
case (name, Some(List(Some(host: ByteString), Some(port: ByteString)))) ⇒
println("Name Host:port: " + name + " " + host.utf8String + ":" + port.utf8String)
Shard("name", host.utf8String, port.utf8String.toInt, None)
}
}
If I understand correctly, it sounds like you want to use map on the request futures to pair each response with the shard's name:
val requestsForMaster: List[Future[(String, Some[List[Some[ByteString]])] =
shardNames.map { x =>
val result = sentinel ? Request("SENTINEL", "get-master-addr-by-name", x)
result.map(x -> _)
}
val mastersConfig = Future.sequence(requestsForMaster)
mastersConfig.onSuccess {
case results: List[(String, Some[List[Some[ByteString]]])] =>
self ! results.map {
case (x, Some(List(Some(host: ByteString), Some(port: ByteString)))) =>
// Create the shard object.
}
}
More generally, if you have a Future[V] and a K, you can create a Future[(K, V)] by writing fv.map(k -> _).
I'm going through log file that is too big to fit into memory and collecting 2 type of expressions, what is better functional alternative to my iterative snippet below?
def streamData(file: File, errorPat: Regex, loginPat: Regex): List[(String, String)]={
val lines : Iterator[String] = io.Source.fromFile(file).getLines()
val logins: mutable.Map[String, String] = new mutable.HashMap[String, String]()
val errors: mutable.ListBuffer[(String, String)] = mutable.ListBuffer.empty
for (line <- lines){
line match {
case errorPat(date,ip)=> errors.append((ip,date))
case loginPat(date,user,ip,id) =>logins.put(ip, id)
case _ => ""
}
}
errors.toList.map(line => (logins.getOrElse(line._1,"none") + " " + line._1,line._2))
}
Here is a possible solution:
def streamData(file: File, errorPat: Regex, loginPat: Regex): List[(String,String)] = {
val lines = Source.fromFile(file).getLines
val (err, log) = lines.collect {
case errorPat(inf, ip) => (Some((ip, inf)), None)
case loginPat(_, _, ip, id) => (None, Some((ip, id)))
}.toList.unzip
val ip2id = log.flatten.toMap
err.collect{ case Some((ip,inf)) => (ip2id.getOrElse(ip,"none") + "" + ip, inf) }
}
Corrections:
1) removed unnecessary types declarations
2) tuple deconstruction instead of ulgy ._1
3) left fold instead of mutable accumulators
4) used more convenient operator-like methods :+ and +
def streamData(file: File, errorPat: Regex, loginPat: Regex): List[(String, String)] = {
val lines = io.Source.fromFile(file).getLines()
val (logins, errors) =
((Map.empty[String, String], Seq.empty[(String, String)]) /: lines) {
case ((loginsAcc, errorsAcc), next) =>
next match {
case errorPat(date, ip) => (loginsAcc, errorsAcc :+ (ip -> date))
case loginPat(date, user, ip, id) => (loginsAcc + (ip -> id) , errorsAcc)
case _ => (loginsAcc, errorsAcc)
}
}
// more concise equivalent for
// errors.toList.map { case (ip, date) => (logins.getOrElse(ip, "none") + " " + ip) -> date }
for ((ip, date) <- errors.toList)
yield (logins.getOrElse(ip, "none") + " " + ip) -> date
}
I have a few suggestions:
Instead of a pair/tuple, it's often better to use your own class. It gives meaningful names to both the type and its fields, which makes the code much more readable.
Split the code into small parts. In particular, try to decouple pieces of code that don't need to be tied together. This makes your code easier to understand, more robust, less prone to errors and easier to test. In your case it'd be good to separate producing your input (lines of a log file) and consuming it to produce a result. For example, you'd be able to make automatic tests for your function without having to store sample data in a file.
As an example and exercise, I tried to make a solution based on Scalaz iteratees. It's a bit longer (includes some auxiliary code for IteratorEnumerator) and perhaps it's a bit overkill for the task, but perhaps someone will find it helpful.
import java.io._;
import scala.util.matching.Regex
import scalaz._
import scalaz.IterV._
object MyApp extends App {
// A type for the result. Having names keeps things
// clearer and shorter.
type LogResult = List[(String,String)]
// Represents a state of our computation. Not only it
// gives a name to the data, we can also put here
// functions that modify the state. This nicely
// separates what we're computing and how.
sealed case class State(
logins: Map[String,String],
errors: Seq[(String,String)]
) {
def this() = {
this(Map.empty[String,String], Seq.empty[(String,String)])
}
def addError(date: String, ip: String): State =
State(logins, errors :+ (ip -> date));
def addLogin(ip: String, id: String): State =
State(logins + (ip -> id), errors);
// Produce the final result from accumulated data.
def result: LogResult =
for ((ip, date) <- errors.toList)
yield (logins.getOrElse(ip, "none") + " " + ip) -> date
}
// An iteratee that consumes lines of our input. Based
// on the given regular expressions, it produces an
// iteratee that parses the input and uses State to
// compute the result.
def logIteratee(errorPat: Regex, loginPat: Regex):
IterV[String,List[(String,String)]] = {
// Consumes a signle line.
def consume(line: String, state: State): State =
line match {
case errorPat(date, ip) => state.addError(date, ip);
case loginPat(date, user, ip, id) => state.addLogin(ip, id);
case _ => state
}
// The core of the iteratee. Every time we consume a
// line, we update our state. When done, compute the
// final result.
def step(state: State)(s: Input[String]): IterV[String, LogResult] =
s(el = line => Cont(step(consume(line, state))),
empty = Cont(step(state)),
eof = Done(state.result, EOF[String]))
// Return the iterate waiting for its first input.
Cont(step(new State()));
}
// Converts an iterator into an enumerator. This
// should be more likely moved to Scalaz.
// Adapted from scalaz.ExampleIteratee
implicit val IteratorEnumerator = new Enumerator[Iterator] {
#annotation.tailrec def apply[E, A](e: Iterator[E], i: IterV[E, A]): IterV[E, A] = {
val next: Option[(Iterator[E], IterV[E, A])] =
if (e.hasNext) {
val x = e.next();
i.fold(done = (_, _) => None, cont = k => Some((e, k(El(x)))))
} else
None;
next match {
case None => i
case Some((es, is)) => apply(es, is)
}
}
}
// main ---------------------------------------------------
{
// Read a file as an iterator of lines:
// val lines: Iterator[String] =
// io.Source.fromFile("test.log").getLines();
// Create our testing iterator:
val lines: Iterator[String] = Seq(
"Error: 2012/03 1.2.3.4",
"Login: 2012/03 user 1.2.3.4 Joe",
"Error: 2012/03 1.2.3.5",
"Error: 2012/04 1.2.3.4"
).iterator;
// Create an iteratee.
val iter = logIteratee("Error: (\\S+) (\\S+)".r,
"Login: (\\S+) (\\S+) (\\S+) (\\S+)".r);
// Run the the iteratee against the input
// (the enumerator is implicit)
println(iter(lines).run);
}
}