来自依赖元素的结构化数组的层次树

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,组合toObjectTreenest来执行完整的转换。

请注意,这里不需要用 nulls 填充的固定长度数组。它通过简单地过滤掉任何空值来与它们一起工作。 (filter (Boolean) 足够简单,但如果这些路径可能是假的——也许是空字符串——那么我们必须用 filter (x => x!= null) 或类似的东西替换它。)虽然这使得它比你的请求,它应该仍然适用于你的数据。


对我来说,这是一种非常强大的数据转换方式:采取多个步骤,每个步骤都将前一个转换为有用的中间格式,朝着您的最终目标前进,然后通过这一系列更简单的简单管道传输数据转换。