如何在Scala中基于深度嵌套的列表字段拆解一个对象?

huangapple go评论79阅读模式
英文:

How to unwind an object based on deeply nested list fields in scala?

问题

简化的情境

让我们用一个包含2个层次嵌套字段的简化示例来说明。

假设我有一个如下所示的case class Person:

case class Address(
  name: String,
  zipCodes: List[String]
)

case class Person(
  name: String,
  address: List[Address]
)

假设我有一个针对上述Person类的object,如下所示:

val person1: Person = Person(
    name = "John",
    address = List(
      Address(
        name = "Address A",
        zipCodes = List("123", "345")
      ),
      Address(
        name = "Address B",
        zipCodes = List("456", "678")
      )
    )
  )

现在,我想以这样一种方式对这个对象person1进行“展开”操作,以便获得以下扁平化的元组列表:

List(    
    ("John", "Address A", "123")
    ("John", "Address A", "345")
    ("John", "Address B", "456")
    ("John", "Address B", "678")
)

在这个简单的情况下,我可以采取以下方法来进行展开:

person1.address.flatMap{
    address => address.zipCodes.map{
      zipCode =>
        (person1.name, address.name, zipCode)
    }
}

问题

在我的实际情况中,我可能有n个层次的嵌套在case class中。因此,为了生成这样的元组,我不太可能为每个内部嵌套字段编写n行flatMaps代码。在Scala中,是否有一种通用(如果可能的话)、更好、更短但仍能实现这一目标的方法?

英文:

Simplified Scenario

Lets take a simplified example with 2 levels of nested fields in a case class.

Suppose I have a case class Person as shown below:

case class Address(
  name: String,
  zipCodes: List[String]
)

case class Person(
  name: String,
  address: List[Address]
)

Suppose I have an object for the above Person class as shown below:

val person1: Person = Person(
    name = "John",
    address = List(
      Address(
        name = "Address A",
        zipCodes = List("123","345")
      ),
      Address(
        name = "Address B",
        zipCodes = List("456","678")
      )
    )
  )

Now, I want to unwind this object person1 in such a way that I get following flattened list of tuples:

List(    
    (John, Address A, 123)
    (John, Address A, 345)
    (John, Address B, 456)
    (John, Address B, 678)
)

Now, in this simple scenario, what I could do to unwind is as follow:

person1.address.flatMap{
    address => address.zipCodes.map{
      zipCode =>
        (person1.name, address.name, zipCode)
    }

Question

In my real scenario, I can have n levels of nesting in the case class. So, it's not feasible for me to write n lines of flatMaps for each inner nested field to produce such tuple. Is there a generic(if possible), better, shorter yet functional way to achieve this in scala ?

答案1

得分: 2

你可以使用 for 来编写示例代码:

for {
  address <- person1.address
  zipCode <- address.zipCodes
} yield {
  (person1.name, address.name, zipCode)
}

使用这个版本,可以轻松地添加所需的多层嵌套。

另一种选项是给每个嵌套类提供一个 flat 方法,该方法使用所包含类的 flat 方法创建类的扁平版本。这意味着调用代码不需要了解嵌套结构,只需在外部对象上调用 flat。或者,如果无法更改原始类,则可以使用类型类(typeclass)。

英文:

You can write your example code using for:

for {
  address &lt;- person1.address
  zipCode &lt;- address.zipCodes
} yield {
  (person1.name, address.name, zipCode)
}

With this version it is easy to add as many levels of nesting as are required.

Another option is to give each nested class a flat method that creates the flattened version of the class using the flat method of the contained classes. This means that calling code does not need to know the nested structure, it just calls flat on the outer object. Or a typeclass could used if you can't change the original classes.

答案2

得分: 0

这个问题似乎是矛盾的 - 一方面,你说你可以有 n 层嵌套(其中 n 可能在编译时不知道),因此你不能写 nflatMap。但另一方面,你又说你想要返回一个元组 - 但是你的示例中元组的长度对应于嵌套的深度,这意味着 n 与元组的长度相同,因此是一个编译时常量。

无论如何,如果你需要任意程度的嵌套,你需要使用递归。

case class RoseTree[A](
  a: A,
  children: List[RoseTree[A]]
)

def unnest[A](tree: RoseTree[A]): List[List[A]] =
  tree match {
    case RoseTree(a, Nil) =>
      List(List(a))
    case RoseTree(a, children) =>
      children.flatMap(x => unnest(x).map(a :: _))
  }

val example = 
  RoseTree("John", List(
    RoseTree("Address A", List(
      RoseTree("123", Nil),
      RoseTree("345", Nil))),
    RoseTree("Address B", List(
      RoseTree("456", Nil),
      RoseTree("678", Nil)))))

assert(unnest(example) == List(
  List("John", "Address A", "123"),
  List("John", "Address A", "345"),
  List("John", "Address B", "456"),
  List("John", "Address B", "678")))
英文:

This question seems contradictory – on the one hand, you're saying that you can have n levels of nesting (where n is presumably not known at compile time), and you therefore can't write n levels of flatMaps. But on the other hand you're saying you want to return a tuple – but the length of the tuple in your example corresponds to the depth of nesting, meaning that n is the same as the length of the tuple and thus a compile-time constant.

Anyway, if you need arbitrary degrees of nesting, you need to use recursion.

case class RoseTree[A](
  a: A,
  children: List[RoseTree[A]]
)

def unnest[A](tree: RoseTree[A]): List[List[A]] =
  tree match {
    case RoseTree(a, Nil) =&gt;
      List(List(a))
    case RoseTree(a, children) =&gt;
      children.flatMap(x =&gt; unnest(x).map(a :: _))
  }

val example = 
  RoseTree(&quot;John&quot;, List(
    RoseTree(&quot;Address A&quot;, List(
      RoseTree(&quot;123&quot;, Nil),
      RoseTree(&quot;345&quot;, Nil))),
    RoseTree(&quot;Address B&quot;, List(
      RoseTree(&quot;456&quot;, Nil),
      RoseTree(&quot;678&quot;, Nil))))

assert(unnest(example) == List(
  List(&quot;John&quot;, &quot;Address A&quot;, &quot;123&quot;),
  List(&quot;John&quot;, &quot;Address A&quot;, &quot;345&quot;),
  List(&quot;John&quot;, &quot;Address B&quot;, &quot;456&quot;),
  List(&quot;John&quot;, &quot;Address B&quot;, &quot;678&quot;)))

huangapple
  • 本文由 发表于 2020年10月14日 00:06:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/64338994.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定