Search code examples
javascriptrecursiontreehierarchyhierarchical-data

Hierarchical Tree from Structured Array of Dependent Elements


I am looking for an efficient approach to build a tree from the following data structure in JavaScript. Each entry always has length 5 and there never exists any gaps (i.e. nulls) between the entries.

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 ]
]

The desired output should look like this:

[{
    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'
            }]
        }]
    }]
}]

Solution

  • You can group the arrays on the first element of each subarray, and then call to_tree to the grouped arrays if they are not empty:

    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)

    Output:

    [
      {
        "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"
              }
            ]
          }
        ]
      }
    ]