Help with for comprehension missing List, Set, and Option

Can someone help me write the for comprehension to collect the values rather then printing them.

I have a data structure which is a Map of Map of Map. When I try to use a for comprehension, I don’t understand the compiler errors. I can use the for comprehension to print (with println) the values I want to collect, but I can’t collect them with yield

I have prepared a ScalaFiddle exhibiting the problem. But here is an explanation for posterity.

val h:HashMap[Int,HashMap[Int,HashMap[List[Int],Set[List[Int]]]]] = someaccessor()

I understand that h(3) gives me a HashMap (or exception), and h.get(3) gives me an Option.
Rather than using nested pattern matches, I would like to use a for comprehension, but I get a bit lost.

When I use the following for comprehension to print, it works fine.

val vec = QmVec(List(List(1,2,3),List(-1,2,3),List(1,-2,3),List(1,2),List(-1,2)))

vec.hash.get(2)
vec.hash.get(2).get(3)
vec.hash.get(2).get(3).get(List(1,2,3))   // Set of Lists of length 3 whose absval are (1,2,3) and exactly 2 of which are positive

for{
  x <- vec.hash.get(2)
  y <- x.get(3)
  (k,v) <- y
  i <- v
} println(s"k=$k   v=$v   i=$i")

This prints the following, which is what I expect. The values of i are printed, correctly.

import dimacs.QmVec
import dimacs.dimacsSimplify._

vec: dimacs.QmVec = dimacs.QmVec@5e7422ef

res0: Option[scala.collection.mutable.HashMap[Int,scala.collection.mutable.HashMap[dimacs.dimacsSimplify.Clause,Set[dimacs.dimacsSimplify.Clause]]]] = Some(Map(2 -> Map(List(1, 2) -> Set(List(1, 2))), 3 -> Map(List(1, 2, 3) -> Set(List(-1, 2, 3), List(1, -2, 3)))))
res1: scala.collection.mutable.HashMap[dimacs.dimacsSimplify.Clause,Set[dimacs.dimacsSimplify.Clause]] = Map(List(1, 2, 3) -> Set(List(-1, 2, 3), List(1, -2, 3)))
res2: Option[Set[dimacs.dimacsSimplify.Clause]] = Some(Set(List(-1, 2, 3), List(1, -2, 3)))

k=List(1, 2, 3)   v=Set(List(-1, 2, 3), List(1, -2, 3))   i=List(-1, 2, 3)
k=List(1, 2, 3)   v=Set(List(-1, 2, 3), List(1, -2, 3))   i=List(1, -2, 3)

Now I want to modify the for comprehension so that it collects the values of i into a Set rather than printing them, so I modify the code as follows.

for{
  x <- vec.hash.get(2)
  y <- x.get(3)     /// flagged in red
  (k,v) <- y        /// flagged in red
  i <- v
} yield i

But in this case I get compiler errors.

found   : scala.collection.mutable.Iterable[dimacs.dimacsSimplify.Clause]
(which expands to)  scala.collection.mutable.Iterable[List[Int]]
required: Option[?]
(k,v) <- y

Opps, there was an error in the ScalaFiddle, I’ve updated it and updated the OP.

The code can be written of course as mentioned above as concentric pattern matches, but this seems overly complicated (when compared to the for comprehension which prints the same values) , as in this ScalaFiddle.

type Clause = List[Int]
type H1 = Map[Clause,Set[Clause]]
type H2 = Map[Int,H1]
type H3 = Map[Int,H2]

val vec:H3 = Map(0->Map(3->Map(List(1,2,3)->Set(List(-1,-2,-3))),
                        1->Map(List(1)    ->Set(List(-1)))),
                 2->Map(3->Map(List(1,2,3)->Set(List(1,2,-3),List(1,-2,3))),
                        2->Map(List(1,3)->Set(List(1,3)),
                               List(1,2)->Set(List(1,2)))))val lengthHash = vec.get(2)

for{
  h2 <- vec.get(2)
  h1 <- h2.get(3)
  (k,v) <- h1
  i <- v
} println(s"i=$i")
  
val clauses = lengthHash match {
  case None => Set()
  case Some(lengthHash) =>
    val rectHash = lengthHash.get(3)
    rectHash match {
      case None => Set()
      case Some(rectHash: Map[Clause, Set[Clause]]) =>
        rectHash.foldLeft(Set[Clause]()) { case (acc: Set[Clause], (_, v: Set[Clause])) => acc union v }
    }
}

println(s"clauses=$clauses")

The compiler uses different methods for desugaring when yield is not used. Your println version desugars to several calls of foreach, which works fine.

With yield, the <- on the Options are desugared to flatMap calls. The function passed to the flatMap is expected to also return an Option, which causes the error. This is the desugared version of your for-yield (with extra type annotations for clarity):

vec.get(2).flatMap{ h2: H2 =>
  h2.get(3).flatMap{ h1: H1 =>
    h1.flatMap{ case (k:Clause, v:Set[Clause]) => // This block is expected
      v.map((i: Clause) => i)                     // to return Option[_]
    }                                             // but h1.flatMap is Iterable
  }
}

This can be solved, however, by converting the Options to a type that’s compatible:

val f: Iterable[Clause] = for{
  h2 <- vec.get(2).toIterable
  h1 <- h2.get(3).toIterable
  (k,v) <- h1
  i <- v
} yield i

You can also use toList or similar instead of toIterable, if you want a more specific iterable type for the result.

2 Likes