scaladata-structurescollectionsfor-comprehension

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


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()


Solution

  • 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.