Scala中flatMap与map的差异及相关用法疑问咨询
val days = List((1, (2, "a")), (1, (3, "b")), (1, (1, "c")), (2, (1, "aa")), (2, (2, "bb"))) println(days) println(days.groupBy(_._1)) val r = days.groupBy(_._1).map(t => { println(s"t = $t, t._1 = ${t._1} t._2 = ${t._2} t._2.map(_._2).toMap = ${t._2.map(_._2).toMap}") t._1 -> t._2.map(_._2).toMap }) println(s"r = $r") val rr = r.flatMap { case (k, v) => Some(k, v) } println(rr) val rrr = r.map { case (k, v) => Some(k, v) } println(rrr)
运行输出:
List((1,(2,a)), (1,(3,b)), (1,(1,c)), (2,(1,aa)), (2,(2,bb))) Map(2 -> List((2,(1,aa)), (2,(2,bb))), 1 -> List((1,(2,a)), (1,(3,b)), (1,(1,c)))) t = (2,List((2,(1,aa)), (2,(2,bb)))), t._1 = 2 t._2 = List((2,(1,aa)), (2,(2,bb))) t._2.map(_._2) = Map(1 -> aa, 2 -> bb) t = (1,List((1,(2,a)), (1,(3,b)), (1,(1,c)))), t._1 = 1 t._2 = List((1,(2,a)), (1,(3,b)), (1,(1,c))) t._2.map(_._2) = Map(2 -> a, 3 -> b, 1 -> c) r = Map(2 -> Map(1 -> aa, 2 -> bb), 1 -> Map(2 -> a, 3 -> b, 1 -> c)) Map(2 -> Map(1 -> aa, 2 -> bb), 1 -> Map(2 -> a, 3 -> b, 1 -> c)) List(Some((2,Map(1 -> aa, 2 -> bb))), Some((1,Map(2 -> a, 3 -> b, 1 -> c))))
1. 为何rr和rrr的结果不同?
(1) 类型差异:Map vs List
这核心是Map的map和flatMap方法行为和你熟悉的List不一样:
- 对于
rrr = r.map { case (k, v) => Some(k, v) }:map的作用是对Map中的每个键值对做转换,这里你把每个(k,v)包装成了Some(k,v)。Map的map返回的是一个Iterable(这里具体表现为List),所以最终得到的是装着Some元素的List。 - 对于
rr = r.flatMap { case (k, v) => Some(k, v) }:flatMap是“先映射,再扁平”的逻辑。Map的flatMap要求你把每个键值对映射成一个可遍历集合(比如Option),然后会把集合里的元素“拆出来”,再重新组合成新的Map。这里Some(k,v)是Option类型(属于可遍历集合,Some含一个元素,None是空集合),所以flatMap会把Some里的(k,v)提取出来,这些键值对最终被组装成了新的Map。
(2) rrr中的Some从何而来?
完全是因为你在map里手动把每个键值对包装成了Some!Some是Option的子类,这里你没有做任何过滤None的操作——如果是想过滤None,通常是在flatMap里返回Option(比如部分键值对返回Some,部分返回None,flatMap会自动去掉None),但你这里只是单纯给每个元素套了个Some,所以结果里就会保留这些Some。
2. 在flatMap中,Some(k, v)是否会被解析为k->v?
是的!因为Option是Scala里的“单元素集合”,当你在Map的flatMap里返回Some(k,v)时,flatMap会遍历这个Option,把里面的(k,v)(也就是k->v的元组)提取出来,然后把所有这样的元组重新组合成一个新的Map。
3. 在flatMap中,Some(a -> (b -> c))是否会被解析为(a, (b, c))?
没错!a -> (b->c)本身就是一个元组(a, (b,c)),你把它包在Some里,flatMap会把这个元组从Some里取出来,然后它就会成为新Map中的一个键值对:键是a,值是(b,c)。
补充:为什么Map的flatMap和List的不一样?
你之前看的教程大多讲List的flatMap,List的flatMap是把每个元素映射成一个List,然后把所有List的元素扁平成一个大List。而Map的flatMap是专门针对键值对设计的:它要求你返回的可遍历集合里的元素必须是键值对(元组),这样才能重新组装成Map。如果你返回的不是键值对,那flatMap会返回一个Iterable而不是Map——比如你要是写r.flatMap { case (k,v) => List(k, v) },得到的就是一个包含所有键和值的List了。
内容的提问来源于stack exchange,提问作者BAE

