如何查看递归数据模型的所有"ancestors"?
How to see all "ancestors" of a recursive data model?
我收到了 JSON:
{
"categories":
[
{
"category_name": "example name",
"children":
[
{
"category_name": "example name"
},
{
...
可以看出,数据是一种递归格式。我能够编写将其解码为我的自定义类型的代码,即:
struct Name: Codable {
let cat: String
let children: [cat]?
}
现在,对于任何一只猫,我都想知道它的“路径”。比如,我想知道所有的超级(祖先)类别是什么。因此,对于类别“平板电脑”,我希望能够遍历向下钻取结构的样子,在这种情况下可能如下所示:
Electronics -> Computers -> Laptops and Tablets -> Tablets
如何构造我的代码或数据模型以便能够检索任何类别的此信息?
这是一个基本的递归解决方案,它将元素的路径添加到数组中,根元素作为第一个,目标元素作为最后一个。
它使用 contains()
所以 Category
需要符合 Equatable
或者可以更改为使用 `contains(where:) 而不是
contains(where: { [=10=].categoryName == target.categoryName })
如果是更实用的解决方案。
func extractChain(for target: Category, parent: Category, chain: inout [Category]) {
guard let children = parent.children else {
chain = []
return
}
chain.append(parent)
if children.contains(target) {
chain.append(target)
return
}
for category in children where category.children != nil {
extractChain(for: target, parent: category, chain: &chain)
if !chain.isEmpty { return }
}
chain = [] // No match, clear the array
}
我只进行了一些基本测试,一项匹配到第 3 级以下,一项没有匹配,因此可能需要进一步测试。
您可以像这样使用 class 和祖先 :
class Category: Codable {
let category_name: String
let children: [Category]?
var ancestor : Category?
func setupAncestor(ancestor: Category?) {
self.ancestor = ancestor
if let children = children {
for child in children {
child.setupAncestor(ancestor: self)
}
}
}
func ancestors() -> [Category] {
if let ancestor = ancestor {
var ancestorList = [ancestor]
ancestorList.append(contentsOf: ancestor.ancestors())
return ancestorList
}
return []
}
func ancestorsNames() -> [String] {
if let ancestor = ancestor {
var ancestorList = [ancestor.category_name]
ancestorList.append(contentsOf: ancestor.ancestorsNames())
return ancestorList
}
return []
}
func displayAncestors() {
for ancestor in ancestors() {
print("Ancestor : \(ancestor.category_name)")
}
}
func display(_ level: Int) {
print("Level \(level)")
print("Category : \(category_name) \(ancestorsNames())")
//displayAncestors()
if let children = children {
for child in children {
child.display(level+1)
}
}
}
}
struct Categories : Codable {
let categories: [Category]
func setupAncestors() {
for category in categories {
category.setupAncestor(ancestor: nil)
}
}
}
let json = "..."
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
do {
let categories = try decoder.decode(Categories.self, from: jsonData!)
categories.setupAncestors()
for category in categories.categories {
category.display(0)
}
} catch {
print("Json decode error : \(error)")
}
您可以更改顺序 and/or return 列表中只有 categoryName
编辑:更正了代码
首先,您需要将路径添加到“类别”,以便有地方存储数据。为了方便起见,我还将添加一个 CategoriesResponse 来处理 top-level 结构,但这并不重要:
struct CategoriesResponse: Decodable {
var categories: [Category]
}
struct Category {
let path: [String]
let categoryName: String
let children: [Category]
}
(我假设你想要的只是父类别的名称。如果你想要某种引用,那是可能的,但数据结构会变得有点复杂。这种基本方法仍然有效,不过。如果您需要类似的东西,请告诉我,我可以扩展答案。)
当然还有标准的 CodingKeys 东西:
private enum CodingKeys: String, CodingKey {
case categoryName = "category_name"
case children
}
解决方案的核心是您需要一个可以接受 KeyedDecodingContainer(而不是解码器)和路径的 init,并处理解码其他所有内容。
// For each element, decode out of the container by hand rather than recursing into init(from: Decoder)
private init(from container: KeyedDecodingContainer<CodingKeys>, path: [String]) throws {
// Track our own path up to this point
self.path = path
// Unload the usual stuff
self.categoryName = try container.decode(String.self, forKey: .categoryName)
// Construct the children, one element at a time (if children exists)
var children: [Category] = []
if container.contains(.children) {
// Extract the array of children
var childrenContainer = try container.nestedUnkeyedContainer(forKey: .children)
while !childrenContainer.isAtEnd {
// Extract the child object
let childContainer = try childrenContainer.nestedContainer(keyedBy: CodingKeys.self)
// For each child, extend the path, decode
let child = try Category(from: childContainer, path: path + [self.categoryName])
// And append
children.append(child)
}
}
self.children = children
}
最后,您需要一个 Decodable 实现来启动它:
extension Category: Decodable {
// Top level decoder to kick everything off
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try self.init(from: container, path: [])
}
}
有了它,它应该使用标准解码器按预期工作:
let categories = try JSONDecoder().decode(CategoriesResponse.self, from: json).categories
我收到了 JSON:
{
"categories":
[
{
"category_name": "example name",
"children":
[
{
"category_name": "example name"
},
{
...
可以看出,数据是一种递归格式。我能够编写将其解码为我的自定义类型的代码,即:
struct Name: Codable {
let cat: String
let children: [cat]?
}
现在,对于任何一只猫,我都想知道它的“路径”。比如,我想知道所有的超级(祖先)类别是什么。因此,对于类别“平板电脑”,我希望能够遍历向下钻取结构的样子,在这种情况下可能如下所示:
Electronics -> Computers -> Laptops and Tablets -> Tablets
如何构造我的代码或数据模型以便能够检索任何类别的此信息?
这是一个基本的递归解决方案,它将元素的路径添加到数组中,根元素作为第一个,目标元素作为最后一个。
它使用 contains()
所以 Category
需要符合 Equatable
或者可以更改为使用 `contains(where:) 而不是
contains(where: { [=10=].categoryName == target.categoryName })
如果是更实用的解决方案。
func extractChain(for target: Category, parent: Category, chain: inout [Category]) {
guard let children = parent.children else {
chain = []
return
}
chain.append(parent)
if children.contains(target) {
chain.append(target)
return
}
for category in children where category.children != nil {
extractChain(for: target, parent: category, chain: &chain)
if !chain.isEmpty { return }
}
chain = [] // No match, clear the array
}
我只进行了一些基本测试,一项匹配到第 3 级以下,一项没有匹配,因此可能需要进一步测试。
您可以像这样使用 class 和祖先 :
class Category: Codable {
let category_name: String
let children: [Category]?
var ancestor : Category?
func setupAncestor(ancestor: Category?) {
self.ancestor = ancestor
if let children = children {
for child in children {
child.setupAncestor(ancestor: self)
}
}
}
func ancestors() -> [Category] {
if let ancestor = ancestor {
var ancestorList = [ancestor]
ancestorList.append(contentsOf: ancestor.ancestors())
return ancestorList
}
return []
}
func ancestorsNames() -> [String] {
if let ancestor = ancestor {
var ancestorList = [ancestor.category_name]
ancestorList.append(contentsOf: ancestor.ancestorsNames())
return ancestorList
}
return []
}
func displayAncestors() {
for ancestor in ancestors() {
print("Ancestor : \(ancestor.category_name)")
}
}
func display(_ level: Int) {
print("Level \(level)")
print("Category : \(category_name) \(ancestorsNames())")
//displayAncestors()
if let children = children {
for child in children {
child.display(level+1)
}
}
}
}
struct Categories : Codable {
let categories: [Category]
func setupAncestors() {
for category in categories {
category.setupAncestor(ancestor: nil)
}
}
}
let json = "..."
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
do {
let categories = try decoder.decode(Categories.self, from: jsonData!)
categories.setupAncestors()
for category in categories.categories {
category.display(0)
}
} catch {
print("Json decode error : \(error)")
}
您可以更改顺序 and/or return 列表中只有 categoryName
编辑:更正了代码
首先,您需要将路径添加到“类别”,以便有地方存储数据。为了方便起见,我还将添加一个 CategoriesResponse 来处理 top-level 结构,但这并不重要:
struct CategoriesResponse: Decodable {
var categories: [Category]
}
struct Category {
let path: [String]
let categoryName: String
let children: [Category]
}
(我假设你想要的只是父类别的名称。如果你想要某种引用,那是可能的,但数据结构会变得有点复杂。这种基本方法仍然有效,不过。如果您需要类似的东西,请告诉我,我可以扩展答案。)
当然还有标准的 CodingKeys 东西:
private enum CodingKeys: String, CodingKey {
case categoryName = "category_name"
case children
}
解决方案的核心是您需要一个可以接受 KeyedDecodingContainer(而不是解码器)和路径的 init,并处理解码其他所有内容。
// For each element, decode out of the container by hand rather than recursing into init(from: Decoder)
private init(from container: KeyedDecodingContainer<CodingKeys>, path: [String]) throws {
// Track our own path up to this point
self.path = path
// Unload the usual stuff
self.categoryName = try container.decode(String.self, forKey: .categoryName)
// Construct the children, one element at a time (if children exists)
var children: [Category] = []
if container.contains(.children) {
// Extract the array of children
var childrenContainer = try container.nestedUnkeyedContainer(forKey: .children)
while !childrenContainer.isAtEnd {
// Extract the child object
let childContainer = try childrenContainer.nestedContainer(keyedBy: CodingKeys.self)
// For each child, extend the path, decode
let child = try Category(from: childContainer, path: path + [self.categoryName])
// And append
children.append(child)
}
}
self.children = children
}
最后,您需要一个 Decodable 实现来启动它:
extension Category: Decodable {
// Top level decoder to kick everything off
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try self.init(from: container, path: [])
}
}
有了它,它应该使用标准解码器按预期工作:
let categories = try JSONDecoder().decode(CategoriesResponse.self, from: json).categories