假设我有一组Action
这样的课程:actions: Set[Action]
,每个Action
课程都有val consequences : Set[Consequence]
,其中Consequence
是案例类。
我希望获得从Consequence
到Set[Action]
的地图,以确定哪些操作会导致特定Consequence
。显然,由于Action
可以有多个Consequence
,因此它可以显示在地图中的多个集合中。
我一直试图解决这个问题(我是Scala的新手),想知道我是否能用map()和groupBy()这样做,但有点迷失。我不希望恢复命令式编程,特别是如果有一些Scala映射函数可以提供帮助。
实现这一目标的最佳方法是什么?
答案 0 :(得分:2)
不完全优雅,因为groupBy
无法处理已在Tuple2
上运行的情况,因此您最终会进行大量的tupling和untupling:
case class Conseq()
case class Action(conseqs: Set[Conseq])
def gimme(actions: Seq[Action]): Map[Conseq, Set[Action]] =
actions.flatMap(a => a.conseqs.map(_ -> a))
.groupBy(_._1)
.mapValues(_.map(_._2)(collection.breakOut))
第一行“拉链”每个动作及其所有后果,产生Seq[(Conseq, Action)]
,将第一个产品元素分组给Map[Conseq, Seq[(Conseq, Action)]
。因此,最后一步需要将地图的值从Seq[(Conseq, Action)]
转换为Set[Action]
。这可以使用mapValues
完成。如果没有显式构建器工厂,它将生成Seq[Action]
,因此必须编写.mapValues(_.map(_._2)).toSet
。将第二个参数列表中的collection.breakOut
传递给map
,可以保存一个步骤,并使map
直接生成Set
集合类型。
另一种可能性是使用嵌套折叠:
def gimme2(actions: Seq[Action]) = (Map.empty[Conseq, Set[Action]] /: actions) {
(m, a) => (m /: a.conseqs) {
(m1, c) => m1.updated(c, m1.getOrElse(c, Set.empty) + a)
}
}
这可能更具可读性。我们从空结果映射开始,遍历操作,并在内部折叠中遍历每个操作的后果,这些后果将合并到结果映射中。