Reputation: 1301
I'm in the situation that I receive a tree data structure from outside, which can be of several different shapes. I need to do a sorta switch case depending on what kind of tree I get. The code I'm writing is beginning to look like this:
val first = Option(root.getA)
.flatMap(o => Option(o.getB))
.flatMap(o => Option(o.getC))
...
val second = Option(root.getA)
.flatMap(o => Option(o.getD))
.flatMap(o => Option(o.getE))
...
first.getOrElse(second.getOrElse...
'first' checks if the tree has the shape root->A->B->C..., 'second' checks if the tree has the shape root->A->D->E... etc. I feel like there ought to be a simpler way to express this idea in code, since all this code is doing is checking for null at every step by wrapping and unwrapping Options, but I can't find it.
Upvotes: 0
Views: 160
Reputation: 6353
You can do this
case class Node(val a: Node, val b: Node, val c: Node, val data: Int)
node match {
case Node(a: Node, b: Node, c: Node, _) => ...
case Node(null, b: Node, c: Node, _) => ...
case Node(a: Node, null, c: Node, _) => ...
case Node(a: Node, b: Node, null, _) => ...
case _ => ...
}
Upvotes: 0
Reputation: 3795
If getA
etc are placeholders for something with parameters like getValue("A")
, you can write a simple function that took root
and ("A", "B", "C")
and easily walk the tree checking for nulls along the way. I assume you've asked the question because it's not this simple. However you could parameterise on method calls using reflection.
Another possibility if the tree is relatively small or your Scala code is performing the majority of the work on it is to recursively copy and transform the tree into a more Scala-like structure that is easier to process. If you are feeling particularly twisted, you could transform it into an XML document and then use pattern-match using XML primitives.
Alternatively, you could write custom extractors if there are relatively few different getX
functions:
object GotA {
def unapply(x: Thing) = Option(x) map {_.getA}
}
Then your code becomes simple pattern-matching like this:
root match {
case GotA(GotB(GotC(x))) => x
case GotA(GotD(GotE(x))) => x
}
If the node-finding rules are so irregular that it's not amenable to any of these approaches, you can at least use a for comprehension that's the equivalent code to what you gave, which you may find more aesthetically pleasing:
val first = for {
a <- Option(root.getA)
b <- Option(a.getB)
c <- Option(b.getC)
} yield c
Sadly, because you've heavily anonymised the question and don't indicate how many of these lookups would be required or how complex they are, I can't recommend a specific solution.
Upvotes: 2
Reputation: 8462
You could do:
val first = Try(root.getA.getB.getC).toOption
But this can be slow, and not recommended. Much better way to make your getN
methods return Option
, and use them as you originally intended, but with for-comprehension:
val first = for {
a <- root.getA
b <- a.getB
c <- b.getC
...
Upvotes: 1