问题
输入一个Map[String, Any],要求过滤掉这个map中所有key=”version”的元素。
示例输入:
val subMap = Map("version" -> "3", "hello" -> "world")
val map = Map("version" -> 1, "query" -> "arganzheng",
"pageNumber" -> "1", "resultPerPage" -> "10", "data" -> subMap)
非函数式的写法
查看了一下scala的集合操作,是有一个filter的操作,但是这个函数只对当前map的元数据进行过滤,并没有递归处理子map。
object MapFilterTest {
def main(args: Array[String]) {
val subMap = Map("version" -> "3", "hello" -> "world")
val map = Map("version" -> 1, "query" -> "arganzheng",
"pageNumber" -> "1", "resultPerPage" -> "10", "data" -> subMap)
println(map)
println(map.filter(_._1 != "version"))
}
}
输出结果如下:
Map(data -> Map(version -> 3, hello -> world), query -> arganzheng, version -> 1, resultPerPage -> 10, pageNumber -> 1)
Map(data -> Map(version -> 3, hello -> world), query -> arganzheng, resultPerPage -> 10, pageNumber -> 1)
并不满足我们的要求,我们需要对map下的子map也调用filter操作。
于是笔者只能自己动手写一个了,像这种需求一般都是简单的递归处理:
object MapFilterTest {
def main(args: Array[String]) {
val subMap = Map("version" -> "3", "hello" -> "world")
val map = Map("version" -> 1, "query" -> "arganzheng",
"pageNumber" -> "1", "resultPerPage" -> "10", "data" -> subMap)
println(map)
val resultMap = visit(map, (key, value) => {
!key.equalsIgnoreCase("version")
})
println(resultMap)
}
def visit(tree: Map[String, Any], accept: (String, Any) => Boolean): Map[String, Any] = {
var resultMap = Map[String, Any]();
for (elem <- tree) {
elem._2 match {
case x: Map[String, Any] => {
resultMap = resultMap.updated(elem._1, visit(x, accept))
}
case _ => {
if (accept(elem._1, elem._2)) resultMap = resultMap + (elem._1 -> elem._2)
}
}
}
return resultMap
}
}
输出结果
Map(data -> Map(version -> 3, hello -> world), query -> arganzheng, version -> 1, resultPerPage -> 10, pageNumber -> 1)
是正确的,但是看起来很别扭,因为用到了可变对象。
标准函数式的写法
于是询问了一个scala大神walt哥,大神给了一个标准的FP写法:
object MapFilterTest {
def main(args: Array[String]) {
val subMap = Map("version" -> "3", "hello" -> "world")
val map = Map("version" -> 1, "query" -> "arganzheng",
"pageNumber" -> "1", "resultPerPage" -> "10", "data" -> subMap)
println(map)
val resultMap = visit(map, (key, value) => {
!key.equalsIgnoreCase("version")
})
println(resultMap)
}
def visit(tree: Map[String, Any], accept: (String, Any) => Boolean): Map[String, Any] = {
tree
.filter { case (k, v) => accept(k, v) }
.map {
case (k, map: Map[String, Any]) => (k, visit(map, accept))
case (k, v) => (k, v)
}
}
}
一看就是典型的FP写法:没有变量、没有可变对象,只是简单的值的变换。
仔细看一下他的实现,其实真的有点类似于写shell管道。scala内建的filter只能对当前的map进行过滤,我们需要对map下的子map也调用filter操作,这不就是map提供的功能吗?不过这里确实避免不了要需要递归。
补记
上面的实现有个bug,就是对于map的value为Map数组/列表的没有过滤,修正了一下:
def visit(tree: Map[String, Any], accept: (String, Any) => Boolean): Map[String, Any] = {
tree
.filter { case (k, v) => accept(k, v) }
.map {
case (k, map: Map[String, Any]) => (k, visit(map, accept))
case (k, v: List[Map[String, Any]]) => (k, v.map(travel(_, accept)))
case (k, v: Vector[Map[String, Any]]) => (k, v.map(travel(_, accept)))
case (k, v) => (k, v)
}
}