在Scala中按Id相交和合并任意贴图

在Scala中按Id相交和合并任意贴图,scala,Scala,在阅读了两个JSON文件后,我得到了以下两个映射: val m1 = Map("events" -> List(Map("id" -> "Beatles", "when" -> "Today"), Map("id"->"Elvis", "when"->"Tomorrow"))) val m2 = Map("events" -> List(Map("id" -> "Beatles", "desc"-> "The greatest band"), Ma

在阅读了两个JSON文件后,我得到了以下两个映射:

val m1 = Map("events" -> List(Map("id" -> "Beatles", "when" -> "Today"), Map("id"->"Elvis", "when"->"Tomorrow")))
val m2 = Map("events" -> List(Map("id" -> "Beatles", "desc"-> "The greatest band"), Map("id"->"BeachBoys","desc"-> "The second best band")))
我希望以一种通用的方式(不参考这两个特定地图的特定结构)合并它们,这样结果将是:

val m3 = Map("events" -> List(Map("id" -> "Beatles", "when" -> "Today", "desc"->"The greatest band")))
也就是说,首先通过id相交,然后连接(两者在相同的深度级别上)。如果它只适用于本例中的最大深度1,那就好了(当然,可以处理任意嵌套的贴图/贴图列表的完全递归解决方案会更好)。这需要以一种完全通用的方式来完成(否则它将是微不足道的),因为两个源JSON文件中的键(如“events”、“id”、“when”、…)都将更改

我在Scalaz/Cats中尝试了(标准)幺半群/半群加法,但是,这当然只是连接列表元素,而不是相交/连接

val m3 = m1.combine(m2) // Cats
// Map(events -> List(Map(id -> Beatles, when -> Today), Map(id -> Elvis, when -> Tomorrow), Map(id -> Beatles, desc -> The greatest band), Map(id -> BeachBoys, desc -> The second best band)))
编辑:映射结构的唯一假设是可能有一个“id”字段。如果存在,则相交并最终连接


一些背景:我有两种JSON文件。一个是静态信息(如乐队描述),另一个是动态信息(如下一场音乐会的日期)。在阅读了这些文件之后,我得到了上面显示的两张地图。我希望避免利用JSON文件的特定结构(例如,通过案例类创建域模型),因为存在完全不同的源文件结构的不同场景,可能会发生更改,因此我不希望在源代码中创建对此文件结构的依赖性。因此,我需要一种通用的方法来合并这两个贴图

所以你有这两张地图

val m1 = Map("events" -> List(Map("id" -> "Beatles", "when" -> "Today"), Map("id"->"Elvis", "when"->"Tomorrow")))
val m2 = Map("events" -> List(Map("id" -> "Beatles", "desc"-> "The greatest band"), Map("id"->"BeachBoys","desc"-> "The second best band")))
而且,看起来您正在尝试对
事件进行分组
,并使用id组成
事件

您的域模型可以用以下案例类表示

case class EventDetails(title: String, desc: String)

case class Event(subjectId: String, eventDetails: EventDetails)

case class EventGroup(subjectId: String, eventDetailsList: List[EventDetails])
让我们将地图转换为更有意义的完整域对象

def eventMapToEvent(eventMap: Map[String, String]): Option[Event] = {
  val subjectIdOpt = eventMap.get("id")
  val (titleOpt, descOpt) = (eventMap - "id").toList.headOption match {
    case Some((title, desc)) => (Some(title), Some(desc))
    case _ => (None, None)
  }

  (subjectIdOpt, titleOpt, descOpt) match {
    case (Some(subjectId), Some(title), Some(desc)) => Some(Event(subjectId, EventDetails(title, desc)))
    case _ => None
  }
}

val m1Events = m1.getOrElse("events", List()).flatMap(eventMapToEvent)
val m2Events = m2.getOrElse("events", List()).flatMap(eventMapToEvent)

val events = m1Events ++ m2Events
现在,与处理地图相比,世界将更有意义。我们可以继续分组

val eventGroups = events.groupBy(event => event.subjectId).map({
  case (subjectId, eventList) => EventGroup(subjectId, eventList.map(event => event.eventDetails)).toList
})
// eventGroups: scala.collection.immutable.Iterable[EventGroup] = List(EventGroup(BeachBoys,List(EventDetails(desc,The second best band))), EventGroup(Elvis,List(EventDetails(when,Tomorrow))), EventGroup(Beatles,List(EventDetails(when,Today), EventDetails(desc,The greatest band))))

可能需要一些澄清。你说“不引用特定结构”,但在那之后,“首先按id相交。”所以我们知道/假设每个
地图
都应该有一个
id
键?还有,这个连接是什么样子的:
Map(id->A,xd->X)
Map(id->A,xd->Y)
?是的,你说得对,这还不清楚!这里有一个字段“id”(带有一个可参数化的字段名)。您的答案对于这个特殊情况是正确的,但正如我所说的,我对通用方法感兴趣。定义“generic”。这是一个标准的map/reduce要求,您的
map
reduce
函数都是特定的,因此我认为您正在寻找另一个简单的map/reduce实现。您可以通过案例类创建域模型。因此,每次JSON源文件中发生结构更改时,都需要采用源代码。由于我想在多种情况下使用这种合并(其中此事件域只是一个示例),我想在没有任何先验结构知识的情况下合并贴图,但可能存在“id”字段,如果存在,则应首先计算此id的交点,然后计算连接。这就是我所说的“泛型”的意思。所以我想,实现这一点的最佳方法是提供一个适当的幺半群加法运算,然后使用combine(cats)/|+|(scalaz),但我不确定如何做到这一点。