Pattern matching against Scala Map entries - scala

Is there any Scala trick to enable pattern matching against map keys? In other words, I'd like to have an extractor that beside the Map instance accepted also a key value that would mean I want this pattern to match only if the matchable value is an instance of Map and there is an entry with the given key in it and the value for this entry be subject to recursive pattern matching.
Something like this:
myMap match {
case MyMap("a")(a) => // do smth with value a
case MyMap("b")(MyMap("c")(c)) => // do smth with value c
}
Update:
I've found some way to approach closer to the goal, but it's still not perfect because it implies definition of synthetic key-value-holders:
case class MapKey[K](key: K) {
def unapply(o: Any) = o match {
case m: Map[K, _] ⇒ m.get(key)
case _ ⇒ None
}
}
val m1 = Map("a" → "aa", "b" → Map("c" → "cc"))
val m2 = Map("a" → "aa", "d" → "dd")
val b = MapKey("b")
val c = MapKey("c")
val d = MapKey("d")
for (m ← List(m1, m2)) m match {
case b(c(x)) ⇒ println(s"b > c: $x")
case d(x) ⇒ println(s"d: $x")
}
Similar question: Can extractors be customized with parameters in the body of a case statement (or anywhere else that an extractor would be used)?
Feature request: SI-5435

Maybe you are looking for solution that you don't really need? Can't imagine extractors here. You can use PF if you want to match key-value pairs:
val map = Map[String, String]("a" -> "b")
def matchTuple[A,B,C](map: Map[A,B])(pf: PartialFunction[(A,B), C]) =
map.collectFirst(pf)
matchTuple(map) {
case ("a", b) => println("value for a is " + b)
}
Return type is Option[Unit] because we use collectFirst and println

Related

Scala: Collect values defined in hashmap passed by a list argument

Suppose I have the following variables:
val m = HashMap( ("1", "one"), ("2", "two"), ("3", "three") )
val l = List("1", "2")
I would like to extract the list List("one","two"), which corresponds to the values for each key in the list present in the map.
This is my solution, works like a charm. Still I would like to know if I'm reinventing the wheel and if there's some idiomatic solution for doing what I intend to do:
class Mapper[T,V](val map: HashMap[T,V]) extends PartialFunction[T, V]{
override def isDefinedAt(x: T): Boolean = map.contains(x)
override def apply(x: T): V = map.get(x) match {
case Some(v) => v
}
}
val collected = l collect (new Mapper(map) )
List("one", "two")
Yes, you are reinventing the wheel. Your code is equivalent to
l collect m
but with additional layer of indirection that doesn't add anything to HashMap (which already implements PartialFunction—just expand the "Linear Supertypes" list to see that).
Alternatively, you can also use flatMap as follows:
l flatMap m.get
The implicit CanBuildFroms make sure that the result is actually a List.
You could do this, which seems a bit simpler:
val res = l.map(m.get(_)) // List(Some("one"), Some("two"))
.flatMap(_.toList)
Or even this, using a for-comprehension:
val res = for {
key <- l
value <- m.get(key)
} yield value
I would suggest something like this:
m.collect { case (k, v) if l.contains(k) => v }
note:
does not preserve the order from l
does not handle the case of duplicates in l

Scala Map Reduction and Aggregation

I have a Map that looks like this and is of Type Map[String, Seq[String]]
Map(
"5" -> Seq("5.1"),
"5.1" -> Seq("5.1.1", "5.1.2"),
"5.1.1" -> Seq("5.1.1.1"),
"5.1.2" -> Seq.empty[String],
"5.1.1.1" -> Seq.empty[String]
)
Given a key, I would like to fetch all the values recursively that belongs to the given key. Say for example., if I want to look up for the key 5, I expect the result to be:
Given Input is: 5
Expected Output is: Seq(5.1, 5.1.1, 5.1.2, 5.1.1.1)
Here is what I tried so far:
def fetchSequence(inputId: String, acc: Seq[String], seqMap: Map[String, Seq[String]]): Seq[String] = seqMap.get(inputId) match {
case None => acc
case Some(subSeq) =>
val newAcc = acc ++ subSeq
subSeq.collect {
case subId=> fetchSequence(subId, newAcc, seqMap)
}.flatten
}
I get an empty result when I call fetchSequence with the Map that I have above.
Somewhat more concise :
def recGet[A](map: Map[A, Seq[A]])(key: A): Seq[A] =
map.get(key).fold(
// return empty Seq if key not found
Seq.empty[A])(
// return a Seq with
// the key and
// the result of recGet called recursively
// (for all the elements in the Seq[A] found for key)
x => Seq(key) ++ x.flatMap(recGet(map)))
You can use recGet as :
val sections = Map(
"5" -> Seq("5.1"),
"5.1" -> Seq("5.1.1", "5.1.2"),
"5.1.1" -> Seq("5.1.1.1"),
"5.1.2" -> Seq.empty[String],
"5.1.1.1" -> Seq.empty[String]
)
recGet(sections)("5") // Seq[String] = List(5, 5.1, 5.1.1, 5.1.1.1, 5.1.2)
recGet(sections)("5.1.1") // Seq[String] = List(5.1.1, 5.1.1.1)
recGet(sections)("5.2") // Seq[String] = List()
This will also give you the (first) element itself (if it exists in the map), if you don't want that, you can probably wrap recGet in another method which uses drop(1) on the result of recGet.

Scala Pattern matching multiple outputs

i am new to scala and got stuck with one pattern matching case.
i am writing a pattern match as below,
case class data(record:String)
def x(input:String)= input match {
case "a" => val a = data("a")
case "b" => val b = data("b")
case anythingElse = val others = ...other object creation
}
i would like to access the variables val a,val b and val others outside of the def x.
is there a way to return val a, val b and val others once the calls to the def x is completed.
Your understanding of pattern matching is not entirely correct, you can assign vals in each case area, but the only purpose of that would be to do something temporary. In scala whatever statement you place last is the return value. This cannot be a val assignment statement, or no value (aka unit) would be returned
def x(input:String)= input match {
case "a" => data("a and something special")
case "b" =>
val temp = "b"+" Hello"
data(temp)
case other => data(other)
}
To my understanding, you are trying to define a new variable in a scope that is outside of each case block:
case class data(record:String)
def x(input:String)= input match {
case "a" => val a = data("a")
case "b" => val b = data("b")
case anythingElse = val others = ...other object creation
}
x("a")
println(a) // should print data("a")
Correct?
That is not going to work! Because Scala "Cannot resolve symbol a". It wasn't defined in the scope where you are using it.
Instead you could propagate it to outer scope using some kind of a data container ( defined in the outer scope ). It could be an array, a variable, or a Map. However this will also make it a mutable container. Not recommended.
A better approach would be to actually return your state from x() method and then use that state. Something like this:
case class Data(record: String)
val template: Map[String, Option[Data]] = List("a", "b", "c").map(_ -> None).toMap[String, Option[Data]]
def x(input: String): Map[String, Option[Data]] = input match {
case "a" => template + ("a" -> Some(Data("a")))
case "b" => template + ("b" -> Some(Data("b")))
case anythingElse => template + ("others" -> Some(Data("others")))
}
val returnMap = x("a")
for (x <- returnMap("a")) {
println(x)
}
OUTPUT:
Data(a)
Values a, b and others are local to each case; consider though
def x(input:String)= input match {
case "a" => (Some(data("a")), None, None)
case "b" => (None, data("b"), None)
case anythingElse = (None, None, `...other object creation`)
}
where the function returns a triplet of Option in which a None represents no matching, and Some(data("...")) a match; hence for instance
val (a,b,others) = x("a")
delivers
a = Some(data("a"))
b = None
others = None

Scala - Map join values to keys?

I have the following map with target node "E":
val map = Map("A" -> "B", "A" -> "C", "C" -> "D", "C" -> "E")
It describe a directed node graph, which looks like:
A
/ \
B C
/ \
D E
I need to enter the graph at any point and generate a route to the target node.
Example 1: Enter at A -> Route: A->C->E
Example 2: Enter at D -> Route: D->C->E
Example 3: Enter at B -> Route: B->A->C->E
Does anyone know of a compact algo which could do this as this must have been attempted before.
Look forward to hearing from you.
Cheers,
Jez
So, here is it:
val map = List("A" -> "B", "A" -> "C", "C" -> "D", "C" -> "E")
def pathOf(tree: Iterable[(String,String)],from: String,to: String, path: List[String] = Nil): List[String] = {
if(from == to) return to::path
tree.filterNot{ case(a,b) => path.contains(a)||path.contains(b) }
.collect{
case (a,b) if a == to => b
case (a,b) if b == to => a
}.map{ x => pathOf(tree,from,x,to::path) }
.find{ _.nonEmpty }
.getOrElse(Nil)
}
Use case:
scala> pathOf(map,"B","E").mkString("->")
res1: String = B->A->C->E
scala> pathOf(map,"C","E").mkString("->")
res2: String = C->E
As relatively new to Scala, I take this problem as a good exercise for myself and would like to share my solution with all of you. Any comments are welcomed!
BTW, the solution given by #Eastsun is a depth-first search which "memorizes" visited nodes in each path, while mine is a breadth-first search where memorization is not required (though you can definitely add this feature to improve efficiency). For trees they yield the same answer but for general graphs they can differ.
The neighbors of each node can also be cached for optimization.
val graph = Vector(("A","B"), ("A","C"), ("C","D"), ("C","E"))
def adjacent(a: String) = {
graph flatMap {
case (`a`, x) => Some(x)
case (x, `a`) => Some(x)
case _ => None
}
}
def go(from: String, to: String) {
def expand(paths: Vector[Vector[String]]) {
paths.find(_.last==to) match {
case Some(x) => println(x); return
case None => expand(paths flatMap { e =>
adjacent(e.last) map (e :+ _)
})
}
}
expand(Vector(Vector(from)))
}
// tests
go("A","E") // Vector(A, C, E)
go("B","E") // Vector(B, A, C, E)
go("D","E") // Vector(D, C, E)
Version with memorization: change
adjacent(e.last) map (e :+ _)
to
adjacent(e.last) filterNot (x => paths.flatten contains x) map (e :+ _)
or put this functionality in the adjacent function.

Pattern match Jackson JSON in Scala

I am wondering if it is possible to do pattern matching on Jackson JSON objects in Scala. We are currently using jackson-module-scala in a Project heavily and would benefit from being able to do pattern matching of Json ObjectNode/JsonNode objects.
If this is not possible, how would I go about adding this functionality? I was thinking something in terms of implicit conversion from JsonNode/ObjectNode to MyClass, where MyClass would have unapply method, doing JsonNode.toString and regex matching. If my logic is correct, I could then do pattern matching on JsonNode objects. Of course, there could be better ways I am not aware of, or this one may not work for reasons I am not yet aware of. To illustrate my case, I would like to be able to perform something in terms of:
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val json = mapper.createObjectNode()
.put("key1","value1")
.put("key2","value2")
.put("key3","value3")
json match {
case MyClass("key1", "value1", "key2", y) => println("Found key1 with value1, where key2 is " + y)
case MyClass("key1", x) => println("Key1 value is " + x)
...
_ => println("No match found")
}
Have you tried to make use of the case class deserialization?
https://github.com/FasterXML/jackson-module-scala/blob/master/src/test/scala/com/fasterxml/jackson/module/scala/deser/CaseClassDeserializerTest.scala
If that doesn't work, I think you would be better off creating extractors to represent your domain objects. Code below assumes a scala Map, but it should give you an idea. No implicit conversion required.
case class DomainObjectA(v1: String, v2: String)
object DomainObjectAExtractor {
def unapply(m: Map[String, String]) = for {
v1 <- m.get("key1")
v2 <- m.get("key2")
} yield DomainObjectA(v1, v2)
}
case class DomainObjectB(v3, v4, v5)
object DomainObjectBExtractor {
def unapply(m: Map[String, String]) = for {
v3 <- m.get("key3")
v4 <- m.get("key4")
v5 <- m.get("key5")
} yield DomainObjectB(v3, v4, v5)
}
json match {
case DomainObjectAExtractor(a#DomainObjectA(_, _)) => a
case DomainObjectBExtractor(b#DomainObjectB(_, _, _)) => b
}
However, if you insist on trying to match against the key/value pairs, there may be ways to accomplish something which is acceptable for you. It is not possible to pass input into the unapply function from the case, which I think would be required if I understand what you want to do correctly. It may be possible to do this with macros which are experimental in the soon-to-be-officially-released scala 2.10. I haven't played with them enough to know if this is or is not possible though.
If ordering of keys was assumed, you could come up with a :: unapply operator similar to :: for list. This could extract the K, V pairs in this known order. Personally, this is too fragile for my tastes.
val json = Map(("key1" -> "one"), ("key2" -> "two"))
object -> {
def unapply[A, B](ab: (A, B)) = Some(ab)
}
object :: {
def unapply[K, V](m: Map[K, V]): Option[((K, V), Map[K, V])] =
m.headOption.map(_ -> m.tail)
}
scala> json match {
| case ("key1" -> "one") :: ("key2" -> value2) :: _ => value2
| }
res0: java.lang.String = two
You would not be able to extract keys in the wrong order though
scala> json match {
| case ("key2" -> value2) :: _ => value2
| case _ => "match fail"
| }
res2: java.lang.String = match fail
You could write Key1, Key2, Key3 as well. This may or may not scale well.
object && {
def unapply[A](a: A) = Some((a, a))
}
object Key2 {
def unapply[V](m: Map[String, V]) = m.get("key2")
}
object Key1 {
def unapply[V](m: Map[String, V]) = m.get("key1")
}
scala> json match {
| case Key2(value2) && Key1(value1) => (value2, value1)
| }
res5: (java.lang.String, java.lang.String) = (two,one)