英文:
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 <- person1.address
zipCode <- 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
可能在编译时不知道),因此你不能写 n
层 flatMap
。但另一方面,你又说你想要返回一个元组 - 但是你的示例中元组的长度对应于嵌套的深度,这意味着 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 flatMap
s. 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) =>
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")))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论