How do I convert a List[Option[(A, List[B])]] to the Option[(A,List[B])]? (Basically retrieve Option[X] from List[Option[X]]) - scala

I have a List of “rules” tuples as follows:
val rules = List[(A, List[B])], where A and B are two separate case-classes
For the purposes of my use-case, I need to convert this to an Option[(A, List[B])]. The A case class contains a property id which is of type Option[String], based on which the tuple is returned.
I have written a function def findRule(entryId: Option[String]), from which I intend to return the tuple (A, List[B]) whose A.id = entryId as an Option. So far, I have written the following snippet of code:
def findRule(entryId: Option[String]) = {
for {
ruleId <- rules.flatMap(_._1.id) // ruleId is a String
id <- entryId // entryId is an Option[String]
} yield {
rules.find(_ => ruleId.equalsIgnoreCase(id)) // returns List[Option[(A, List[B])]
}
}
This snippet returns a List[Option[(A, List[B])] but I can’t figure out how to retrieve just the Option from it. Using .head() isn’t an option, since the rules list may be empty. Please help as I am new to Scala.
Example (the real examples are too large to post here, so please consider this representative example):
val rules = [(A = {id=1, ….}, B = [{B1}, {B2}, {B3}, …]), (A={id=2, ….}, B = [{B10}, {B11}, {B12}, …]), …. ] (B is not really important here, I need to find the tuple based on the id element of case-class A)
Now, suppose entryId = Some(1)
After the findRule() function, this would currently look like:
[Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …]))]
I want to return:

Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …])) , ie, the Option within the List returned (currently) from findRule()

From your question, it sounds like you're trying to pick a single item from your list based on some conditional, which means you'll probably want to start with rules.find. The problem then becomes how to express the predicate function that you pass to find.
From my read of your question, the conditional is
The id on the A part of the tuple needs to match the entryId that was passed in elsewhere
def findRule(entryId: Option[String]) =
rules.find { case (a, listOfB) => entryId.contains(a.id) }
The case expression is just nice syntax for dealing with the tuple. I could have also done
rules.find { tup => entryId.contains(tup._1.id) }
The contains method on Option roughly does "if I'm a Some, see if my value equals the argument; if I'm a None, just return false and ignore the argument". It works as a comparison between the Option[String] you have for entryId and the plain String you have for A's id.
Your first attempt didn't work because rules.flatMap got you a List, and using that after the <- in the for-comprehension means another flatMap, which keeps things as a List.

A variant that I personally prefer is
def findRule(entryId: Option[String]): Option[(A, List[B])] = {
entryId.flatMap { id =>
rules.find { case (a, _) => a.id == id }
}
}
In the case where entryId is None, it just immediately returns None. If you start with rules.find, then it will iterate through all of the rules and check each one even when entryId is None. It's unlikely to make any observable performance difference if the list of rules is small, but I also find it more intuitive to understand.

Related

How to functionally handle a logging side effect

I want to log in the event that a record doesn't have an adjoining record. Is there a purely functional way to do this? One that separates the side effect from the data transformation?
Here's an example of what I need to do:
val records: Seq[Record] = Seq(record1, record2, ...)
val accountsMap: Map[Long, Account] = Map(record1.id -> account1, ...)
def withAccount(accountsMap: Map[Long, Account])(r: Record): (Record, Option[Account]) = {
(r, accountsMap.get(r.id))
}
def handleNoAccounts(tuple: (Record, Option[Account]) = {
val (r, a) = tuple
if (a.isEmpty) logger.error(s"no account for ${record.id}")
tuple
}
def toRichAccount(tuple: (Record, Option[Account]) = {
val (r, a) = tuple
a.map(acct => RichAccount(r, acct))
}
records
.map(withAccount(accountsMap))
.map(handleNoAccounts) // if no account is found, log
.flatMap(toRichAccount)
So there are multiple issues with this approach that I think make it less than optimal.
The tuple return type is clumsy. I have to destructure the tuple in both of the latter two functions.
The logging function has to handle the logging and then return the tuple with no changes. It feels weird that this is passed to .map even though no transformation is taking place -- maybe there is a better way to get this side effect.
Is there a functional way to clean this up?
I could be wrong (I often am) but I think this does everything that's required.
records
.flatMap(r =>
accountsMap.get(r.id).fold{
logger.error(s"no account for ${r.id}")
Option.empty[RichAccount]
}{a => Some(RichAccount(r,a))})
If you're using scala 2.13 or newer you could use tapEach, which takes function A => Unit to apply side effect on every element of function and then passes collection unchanged:
//you no longer need to return tuple in side-effecting function
def handleNoAccounts(tuple: (Record, Option[Account]): Unit = {
val (r, a) = tuple
if (a.isEmpty) logger.error(s"no account for ${record.id}")
}
records
.map(withAccount(accountsMap))
.tapEach(handleNoAccounts) // if no account is found, log
.flatMap(toRichAccount)
In case you're using older Scala, you could provide extension method (updated according to Levi's Ramsey suggestion):
implicit class SeqOps[A](s: Seq[A]) {
def tapEach(f: A => Unit): Seq[A] = {
s.foreach(f)
s
}
}

Consolidate a list of Futures into a Map in Scala

I have two case classes P(id: String, ...) and Q(id: String, ...), and two functions returning futures:
One that retrieves a list of objects given a list of id-s:
def retrieve(ids: Seq[String]): Future[Seq[P]] = Future { ... }
The length of the result might be shorter than the input, if not all id-s were found.
One that further transforms P to some other type Q:
def transform(p: P): Future[Q] = Future { ... }
What I would like in the end is, the following. Given ids: Seq[String], calculate a Future[Map[String, Option[Q]]].
Every id from ids should be a key in the map, with id -> Some(q) when it was retrieved successfully (ie. present in the result of retrieve) and also transformed successfully. Otherwise, the map should contain id -> None or Empty.
How can I achieve this?
Is there an .id property on P or Q? You would need one to create the map. Something like this?
for {
ps <- retrieve(ids)
qs <- Future.sequence(ps.map(p => transform(p))
} yield ids.map(id => id -> qs.find(_.id == id)).toMap
Keep in mind that Map[String,Option[X]] is usually not necessary, since if you have Map[String,X] the .get method on the map will give you an Option[X].
Edit: Now assumes that P has a member id that equals the original id-String, otherwise the connection between ids and ps gets lost after retrieve.
def consolidatedMap(ids: Seq[String]): Future[Map[String, Option[Q]]] = {
for {
ps <- retrieve(ids)
qOpts <- Future.traverse(ps){
p => transform(p).map(Option(_)).recover {
// TODO: don't sweep `Throwable` under the
// rug in your real code
case t: Throwable => None
}
}
} yield {
val qMap = (ps.map(_.id) zip qOpts).toMap
ids.map{ id => (id, qMap.getOrElse(id, None)) }.toMap
}
}
Builds an intermediate Map from retrieved Ps and transformed Qs, so that building of ids-to-q-Options map works in linear time.

Scalatest "At least one" instead for Forall

I wrote this test method in Scala to test a REST service.
#Test def whenRequestProductInfo() {
// When Request Product Info
forAll { (productId: Int) =>
val result = mockMvc().perform(get(s"/products/$productId")
.accept(MediaType.parseMediaType(APPLICATION_JSON_CHARSET_UTF_8)))
.andExpect(status.isOk)
.andExpect(content.contentType(APPLICATION_JSON_CHARSET_UTF_8))
.andReturn;
val productInfo = mapper.readValue(result.getResponse.getContentAsString, classOf[ProductInfo])
// At least one is not null
// assert(productInfo.getInventoryInfo != null)
}
}
But I want to test that at least one productInfo.getInventoryInfo is not null instead of every productInfo.getInventoryInfo is not null.
Assuming we have a list of product ids:
val productIds: List[Int] = ???
We ought to factor the conversion from productId to productInfo into another val. (I would think this method or something similar would exist in your code elsewhere).
val inventoryInfo = productIds.map { case productId =>
val result = mockMvc().perform(get(s"/products/$productId")
.accept(MediaType.parseMediaType(APPLICATION_JSON_CHARSET_UTF_8)))
.andExpect(status.isOk)
.andExpect(content.contentType(APPLICATION_JSON_CHARSET_UTF_8))
.andReturn
val productInfo = mapper.readValue(result.getResponse.getContentAsString, classOf[ProductInfo])
productInfo.getInventoryInfo
}
Now we have a list of inventory info, whatever type that is. We can use atLeast to check that at least one inventory info from the collection is not null.
atLeast(1, inventoryInfo) should not be null
It doesn't seem like ScalaTest has any curried version of this like with forAll, so the syntax is much different and not quite as nice if you need to do a bunch of computations.
forAll can be passed configuration for the number of successful evaluations needed and number of failed evaluations allowed. This should accomplish what you are looking for. Documentation here at the end of the page.
Example:
forAll (minSuccessful(1)) { (productId: Int) => ...

How to handle Option of List in Scala?

Suppose I have a function getCustomers and getOrdersByCustomer.
def getCustomer():List[Customer] = ...
def getOrdersByCustomer(cust: Customer): List[Order] = ...
Now I can easily define a function getOrdersOfAllCustomers
def getOrdersOfAllCustomers(): List[Order] =
for(cust <- getCustomer(); order <- getOrderByCustomer(cust)) yield order
So far, so good but what if getCustomer and getOrdersByCustomer return Options of the lists ?
def getCustomer():Option[List[Customer]] = ...
def getOrdersByCustomer(cust: Customer): Option[List[Order]] = ...
Now I would like to implement two different flavors of getOrdersOfAllCustomers():
Return None if one of the functions returns None;
Return None if getCustomer returns None and do not care if getOrdersByCustomer returns None.
How would you suggest implement it?
I think you should consider three possibilities--a populated list, an empty list, or an error--and avoid a lot of inelegant testing to figure out which one happened.
So use Try with List:
def getOrdersOfAllCustomers(): Try[List[Order]] = {
Try(funtionReturningListOfOrders())
}
If all goes well, you will come out with a Success[List[Order]]; if not, Failure[List[Order]].
The beauty of this approach is no matter which happens--a populated list, an empty list, or an error--you can do all the stuff you want with lists. This is because Try is a monad just like Option is. Go ahead and filter, forEach, map, etc. to your heart's content without caring which of those three occurred.
The one thing is that awkward moment when you do have to figure out if success or failure happened. Then use a match expression:
getOrdersOfAllCustomers() match {
case Success(orders) => println(s"Awww...yeah!")
case Failure(ex) => println(s"Stupid Scala")
}
Even if you don't go with the Try, I implore you not to treat empty lists different from populated lists.
Try this,
def getOrdersOfAllCustomers(): Option[List[Order]] =
for{
cust <- getCustomer().toList.flatten;
order <- getOrderByCustomer(cust).toList.flatten
} yield order
This should do it:
def getOrdersOfAllCustomers(): Option[List[Order]] = {
getCustomer() flatMap { customers =>
//optOrders is a List[Option[List[Order]]]
val optOrders = customers map { getOrderByCustomer }
// Any result must be wrapped in an Option because we're flatMapping
// the return from the initial getCustomer call
if(optOrders contains None) None
else {
// map the nested Option[List[Order]]] into List[List[Order]]
// and flatten into a List[Order]
// This then gives a List[List[Order]] which can be flattened again
Some(optOrders.map(_.toList.flatten).flatten)
}
}
}
The hard part is handling the case where one of the nested invocations of getOrderByCustomer returns None and bubbling that result back to the outer scope (which is why using empty lists is so much easier)

Scala - How to group a list of tuples without pattern matching?

Consider the following structure (in reality the structure is a bit more complex):
case class A(id:String,name:String) {
override def equals(obj: Any):Boolean = {
if (obj == null || !obj.isInstanceOf[A]) return false
val a = obj.asInstanceOf[A]
name == a.name
}
override def hashCode() = {
31 + name.hashCode
}
}
val a1 = A("1","a")
val a2 = A("2","a")
val a3 = A("3","b")
val list = List((a1,a2),(a1,a3),(a2,a3))
Now let's say I want to group all tuples with equal A's. I could implement it like this
list.groupBy {
case (x,y) => (x,y)
}
But, I don't like to use pattern matching here, because it's not adding anything here. I want something simple, like this:
list.groupBy(_)
Unfortunately, this doesn't compile. Not even when I do:
list.groupBy[(A,A)](_)
Any suggestions how to simplify my code?
list.groupBy { case (x,y) => (x,y) }
Here you are deconstructing the tuple into its two constituent parts, just to immediately reassemble them exactly like they were before. In other words: you aren't actually doing anything useful. The input and output are identical. This is just the same as
list.groupBy { t => t }
which is of course just the identity function, which Scala helpfully provides for us:
list groupBy identity
If you want to group the elements of a list accoding to their own equals method, you only need to pass the identity function to groupBy:
list.groupBy(x=>x)
It's not enough to write list.groupBy(_) because of the scope of _, that is it would be desugared to x => list.groupBy(x), which is of course not what you want.