来自依赖元素的结构化数组的层次树
Hierarchical Tree from Structured Array of Dependent Elements
我正在寻找一种从 JavaScript 中的以下数据结构构建树的有效方法。每个条目的长度始终为 5,并且条目之间不存在任何间隙(即空值)。
var geographies = [
[ 'Denmark', 'Midtjylland', null, null, null ],
[ 'Denmark', 'Syddanmark', 'Langeland', null, null ],
[ 'Denmark', 'Syddanmark', 'Ærø', null, null ],
[ 'Japan', 'Okinawa', 'Izenajima', null, null ],
[ 'Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null ]
]
所需的输出应如下所示:
[{
label: "Denmark",
children: [{
label: "Midtjylland",
},
{
label: "Syddanmark",
children: [{
label: "Langeland"
},
{
label: "Ærø"
}]
}
]
}, {
label: "Japan",
children: [{
label: "Okinawa",
children: [{
label: "Izenajima"
}]
}, {
label: "Hokkaido",
children: [{
label: "Rishiri",
children: [{
label: 'Rishiri-to'
}]
}]
}]
}]
可以在每个子数组的第一个元素上对数组进行分组,如果分组数组不为空则调用to_tree
:
function to_tree(d){
var c = {}
for (var i of d){
var k = i.shift();
c[k] = k in c ? [...c[k], i] : [i]
}
return Object.keys(c).map(function(x){return {label:x, ...(c[x].filter(j => j.length).length ? {children:to_tree(c[x].filter(j => j.length))} : {})}})
}
var geographies = [[ 'Denmark', 'Midtjylland', null, null, null ], [ 'Denmark', 'Syddanmark', 'Langeland', null, null ], [ 'Denmark', 'Syddanmark', 'Ærø', null, null ], [ 'Japan', 'Okinawa', 'Izenajima', null, null ], [ 'Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null ]]
var geographies1 = [[ 'Greece', null, null, null, null ], [ 'Greece', 'Ionian Islands', 'Lefkada', null, null ], [ 'Greece', 'Attica', 'Salamis', null, null ], [ 'Greece', 'Ionian Islands', 'Cephalonia', null, null ], [ 'Greece', 'Thessaly', 'Skiathos', null, null ], [ 'Greece', 'South Aegean', 'Kea', null, null ], [ 'Greece', 'Attica', 'Kythira', null, null ]]
var r = to_tree(geographies.map(x => x.filter(y => y != null)));
var r1 = to_tree(geographies1.map(x => x.filter(y => y != null)));
console.log(r)
console.log(r1)
输出:
[
{
"label": "Denmark",
"children": [
{
"label": "Midtjylland"
},
{
"label": "Syddanmark",
"children": [
{
"label": "Langeland"
},
{
"label": "Ærø"
}
]
}
]
},
{
"label": "Japan",
"children": [
{
"label": "Okinawa",
"children": [
{
"label": "Izenajima"
}
]
},
{
"label": "Hokkaido",
"children": [
{
"label": "Rishiri",
"children": [
{
"label": "Rishiri-to"
}
]
}
]
}
]
}
]
[
{
"label": "Greece",
"children": [
{
"label": "Ionian Islands",
"children": [
{
"label": "Lefkada"
},
{
"label": "Cephalonia"
}
]
},
{
"label": "Attica",
"children": [
{
"label": "Salamis"
},
{
"label": "Kythira"
}
]
},
{
"label": "Thessaly",
"children": [
{
"label": "Skiathos"
}
]
},
{
"label": "South Aegean",
"children": [
{
"label": "Kea"
}
]
}
]
}
]
此版本进行双重转换,首先以这种形式递归嵌套您的数据:
{
"Denmark": {
"Midtjylland": {},
"Syddanmark": {
"Langeland": {},
"Ærø": {}
}
},
"Japan": {
"Okinawa": {
"Izenajima": {}
},
"Hokkaido": {
"Rishiri": {
"Rishiri-to": {}
}
}
}
}
然后递归地将其转换为您请求的输出。
没有根本原因不能一次完成。但我经常发现需要像中间格式这样的东西,因此有工具可以进行这种转换。从那里最终转换非常简单。
// utility functions
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const nest = (xs) =>
xs .reduce ((o, p) => setPath (p .filter (Boolean)) ({}) (o), {})
// helper function
const toObjectTree = (o) =>
Object .entries (o) .map (([name, kids]) => ({
label: name,
...(Object .keys (kids) .length ? {children: toObjectTree (kids)} : {})
}))
// main function
const convert = (xs) =>
toObjectTree (nest (xs))
// sample data
const geographies1 = [['Denmark', 'Midtjylland', null, null, null], ['Denmark', 'Syddanmark', 'Langeland', null, null], ['Denmark', 'Syddanmark', 'Ærø', null, null], ['Japan', 'Okinawa', 'Izenajima', null, null], ['Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null]]
const geographies2 = [[ 'Greece', null, null, null, null ], [ 'Greece', 'Ionian Islands', 'Lefkada', null, null ], [ 'Greece', 'Attica', 'Salamis', null, null ], [ 'Greece', 'Ionian Islands', 'Cephalonia', null, null ], [ 'Greece', 'Thessaly', 'Skiathos', null, null ], [ 'Greece', 'South Aegean', 'Kea', null, null ], [ 'Greece', 'Attica', 'Kythira', null, null ]]
const geographies3 = [[ 'Denmark', 'Syddanmark', 'Langeland', null, null ], [ 'Denmark', 'Syddanmark', null, null, null ], [ 'Denmark', 'Syddanmark', 'Ærø', null, null ], [ 'Japan', 'Okinawa', 'Izenajima', null, null ], [ 'Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null ]]
// demo
console .log (convert (geographies1))
console .log (convert (geographies2))
console .log (convert (geographies3))
.as-console-wrapper {max-height: 100% !important; top: 0}
我们从效用函数 setPath
开始,您可以在我的一些 other answers 中找到更详细的描述。不过,简而言之,它使用一个字符串数组 and/or 整数来设置所提供对象(的副本)中嵌套路径的值。
然后nest
从这样的路径数组构建一个对象,叶节点是空对象。
我们编写了一个辅助函数 toObjectTree
以递归方式将其转换为您的 label
/children
结构。
最后是主函数convert
,组合toObjectTree
和nest
来执行完整的转换。
请注意,这里不需要用 null
s 填充的固定长度数组。它通过简单地过滤掉任何空值来与它们一起工作。 (filter (Boolean)
足够简单,但如果这些路径可能是假的——也许是空字符串——那么我们必须用 filter (x => x!= null)
或类似的东西替换它。)虽然这使得它比你的请求,它应该仍然适用于你的数据。
对我来说,这是一种非常强大的数据转换方式:采取多个步骤,每个步骤都将前一个转换为有用的中间格式,朝着您的最终目标前进,然后通过这一系列更简单的简单管道传输数据转换。
我正在寻找一种从 JavaScript 中的以下数据结构构建树的有效方法。每个条目的长度始终为 5,并且条目之间不存在任何间隙(即空值)。
var geographies = [
[ 'Denmark', 'Midtjylland', null, null, null ],
[ 'Denmark', 'Syddanmark', 'Langeland', null, null ],
[ 'Denmark', 'Syddanmark', 'Ærø', null, null ],
[ 'Japan', 'Okinawa', 'Izenajima', null, null ],
[ 'Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null ]
]
所需的输出应如下所示:
[{
label: "Denmark",
children: [{
label: "Midtjylland",
},
{
label: "Syddanmark",
children: [{
label: "Langeland"
},
{
label: "Ærø"
}]
}
]
}, {
label: "Japan",
children: [{
label: "Okinawa",
children: [{
label: "Izenajima"
}]
}, {
label: "Hokkaido",
children: [{
label: "Rishiri",
children: [{
label: 'Rishiri-to'
}]
}]
}]
}]
可以在每个子数组的第一个元素上对数组进行分组,如果分组数组不为空则调用to_tree
:
function to_tree(d){
var c = {}
for (var i of d){
var k = i.shift();
c[k] = k in c ? [...c[k], i] : [i]
}
return Object.keys(c).map(function(x){return {label:x, ...(c[x].filter(j => j.length).length ? {children:to_tree(c[x].filter(j => j.length))} : {})}})
}
var geographies = [[ 'Denmark', 'Midtjylland', null, null, null ], [ 'Denmark', 'Syddanmark', 'Langeland', null, null ], [ 'Denmark', 'Syddanmark', 'Ærø', null, null ], [ 'Japan', 'Okinawa', 'Izenajima', null, null ], [ 'Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null ]]
var geographies1 = [[ 'Greece', null, null, null, null ], [ 'Greece', 'Ionian Islands', 'Lefkada', null, null ], [ 'Greece', 'Attica', 'Salamis', null, null ], [ 'Greece', 'Ionian Islands', 'Cephalonia', null, null ], [ 'Greece', 'Thessaly', 'Skiathos', null, null ], [ 'Greece', 'South Aegean', 'Kea', null, null ], [ 'Greece', 'Attica', 'Kythira', null, null ]]
var r = to_tree(geographies.map(x => x.filter(y => y != null)));
var r1 = to_tree(geographies1.map(x => x.filter(y => y != null)));
console.log(r)
console.log(r1)
输出:
[
{
"label": "Denmark",
"children": [
{
"label": "Midtjylland"
},
{
"label": "Syddanmark",
"children": [
{
"label": "Langeland"
},
{
"label": "Ærø"
}
]
}
]
},
{
"label": "Japan",
"children": [
{
"label": "Okinawa",
"children": [
{
"label": "Izenajima"
}
]
},
{
"label": "Hokkaido",
"children": [
{
"label": "Rishiri",
"children": [
{
"label": "Rishiri-to"
}
]
}
]
}
]
}
]
[
{
"label": "Greece",
"children": [
{
"label": "Ionian Islands",
"children": [
{
"label": "Lefkada"
},
{
"label": "Cephalonia"
}
]
},
{
"label": "Attica",
"children": [
{
"label": "Salamis"
},
{
"label": "Kythira"
}
]
},
{
"label": "Thessaly",
"children": [
{
"label": "Skiathos"
}
]
},
{
"label": "South Aegean",
"children": [
{
"label": "Kea"
}
]
}
]
}
]
此版本进行双重转换,首先以这种形式递归嵌套您的数据:
{
"Denmark": {
"Midtjylland": {},
"Syddanmark": {
"Langeland": {},
"Ærø": {}
}
},
"Japan": {
"Okinawa": {
"Izenajima": {}
},
"Hokkaido": {
"Rishiri": {
"Rishiri-to": {}
}
}
}
}
然后递归地将其转换为您请求的输出。
没有根本原因不能一次完成。但我经常发现需要像中间格式这样的东西,因此有工具可以进行这种转换。从那里最终转换非常简单。
// utility functions
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const nest = (xs) =>
xs .reduce ((o, p) => setPath (p .filter (Boolean)) ({}) (o), {})
// helper function
const toObjectTree = (o) =>
Object .entries (o) .map (([name, kids]) => ({
label: name,
...(Object .keys (kids) .length ? {children: toObjectTree (kids)} : {})
}))
// main function
const convert = (xs) =>
toObjectTree (nest (xs))
// sample data
const geographies1 = [['Denmark', 'Midtjylland', null, null, null], ['Denmark', 'Syddanmark', 'Langeland', null, null], ['Denmark', 'Syddanmark', 'Ærø', null, null], ['Japan', 'Okinawa', 'Izenajima', null, null], ['Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null]]
const geographies2 = [[ 'Greece', null, null, null, null ], [ 'Greece', 'Ionian Islands', 'Lefkada', null, null ], [ 'Greece', 'Attica', 'Salamis', null, null ], [ 'Greece', 'Ionian Islands', 'Cephalonia', null, null ], [ 'Greece', 'Thessaly', 'Skiathos', null, null ], [ 'Greece', 'South Aegean', 'Kea', null, null ], [ 'Greece', 'Attica', 'Kythira', null, null ]]
const geographies3 = [[ 'Denmark', 'Syddanmark', 'Langeland', null, null ], [ 'Denmark', 'Syddanmark', null, null, null ], [ 'Denmark', 'Syddanmark', 'Ærø', null, null ], [ 'Japan', 'Okinawa', 'Izenajima', null, null ], [ 'Japan', 'Hokkaido', 'Rishiri', 'Rishiri-to', null ]]
// demo
console .log (convert (geographies1))
console .log (convert (geographies2))
console .log (convert (geographies3))
.as-console-wrapper {max-height: 100% !important; top: 0}
我们从效用函数 setPath
开始,您可以在我的一些 other answers 中找到更详细的描述。不过,简而言之,它使用一个字符串数组 and/or 整数来设置所提供对象(的副本)中嵌套路径的值。
然后nest
从这样的路径数组构建一个对象,叶节点是空对象。
我们编写了一个辅助函数 toObjectTree
以递归方式将其转换为您的 label
/children
结构。
最后是主函数convert
,组合toObjectTree
和nest
来执行完整的转换。
请注意,这里不需要用 null
s 填充的固定长度数组。它通过简单地过滤掉任何空值来与它们一起工作。 (filter (Boolean)
足够简单,但如果这些路径可能是假的——也许是空字符串——那么我们必须用 filter (x => x!= null)
或类似的东西替换它。)虽然这使得它比你的请求,它应该仍然适用于你的数据。
对我来说,这是一种非常强大的数据转换方式:采取多个步骤,每个步骤都将前一个转换为有用的中间格式,朝着您的最终目标前进,然后通过这一系列更简单的简单管道传输数据转换。