从平面数组创建嵌套数组-数据结构

时间:2020-06-02 03:20:57

标签: javascript arrays algorithm recursion data-structures

我需要使用路径作为子代的引用来创建嵌套数组。 例如:4.1是4的孩子,4.1.1是4.1的孩子,4.2是4的孩子... 我有这个平面数组,其中包含所有数据和路径。创建嵌套数组的最佳方法是什么,在该嵌套数组中,子级将基于其路径嵌套到其父级。

输入:

const list = [
  {
    location: 1,
    path: '4'
  },
  {
    location: 2,
    path: '4.1'
  },  
  {
    location: 3,
    path: '4.1.1'
  },  
  {
    location: 4,
    path: '4.1.2'
  },  
  {
    location: 5,
    path: '4.2'
  },  
  {
    location: 6,
    path: '4.2.1'
  },
  {
    location: 7,
    path: '4.3'
  },
  {
    location: 8,
    path: '4.3.1'
  }
];

输出:

const  list = [
  {
    location: 1,
    path: '4',
        children: [
            {
            location: 2,
            path: '4.1',
            children: [
                {
                    location: 3,
                    path: '4.1.1'
                },  
                {
                    location: 4,
                    path: '4.1.2'
                },  
            ]
        },  
            {
                location: 5,
                path: '4.2',
                children: [
                    {
                        location: 6,
                        path: '4.2.1'
                    },
                ]
            },  
            {
                location: 7,
                path: '4.3',
                children: [
                    {
                        location: 8,
                        path: '4.3.1'
                    }
                ]
            },
        ]
  },
];

最好的方法是递归。 对这个算法有什么建议吗?

5 个答案:

答案 0 :(得分:1)

您可以先按路径对对象数组进行排序,以使父对象始终位于排序数组中的子对象之前。 例如:“ 4”将在“ 4.1”之前

现在,您可以创建一个以键为路径的对象。假设我们的对象中已经插入了“ 4”。

obj = {
  '4': {
    "location": 1,
    "path": "4",    
  }
}

当我们处理'4.1'时,我们首先检查对象中是否存在'4'。如果是,则现在进入其子项(如果不存在键“ children”,则创建一个新的空对象)并检查是否存在“ 4.1”。如果没有,我们插入“ 4.1”

obj = {
  '4': {
    "location": 1,
    "path": "4",
    "children": {
      "4.1": {
        "location": 2,
        "path": "4.1"
      }
    }
  }
}

我们对列表中的每个元素重复此过程。最后,我们只需要将该对象递归转换为对象数组即可。

最终代码:

list.sort(function(a, b) {
  return a.path - b.path;
})

let obj = {}

list.forEach(x => {
  let cur = obj;
  for (let i = 0; i < x.path.length; i += 2) {
    console.log(x.path.substring(0, i + 1))
    if (x.path.substring(0, i + 1) in cur) {
      cur = cur[x.path.substring(0, i + 1)]
      if (!('children' in cur)) {
        cur['children'] = {}
      }
      cur = cur['children']
    } else {
      break;
    }
  }
  cur[x.path] = x;
})

function recurse (obj) {
  let res = [];
  Object.keys(obj).forEach((key) => {
    if (obj[key]['children'] !== null && typeof obj[key]['children'] === 'object') {
      obj[key]['children'] = recurse(obj[key]['children'])
    }
    res.push(obj[key])
  })
  return res;
}

console.log(recurse(obj));

答案 1 :(得分:1)

执行此操作的一种方法是使用中间索引将路径映射到对象,然后通过在索引中查找每个节点及其父级将列表折叠为结构。如果没有父项,则将其添加到根对象中。最后,我们返回根对象的子代。这是一些代码:

const restructure = (list) => {
  const index = list .reduce(
    (a, {path, ...rest}) => ({...a, [path]: {path, ...rest}}), 
    {}
  )
  
  return list .reduce((root, {path}) => {
    const node = index [path]
    const parent = index [path .split('.') .slice(0, -1) .join('.')] || root
    parent.children = [...(parent.children || []), node]
    return root
  }, {children: []}) .children
}

const list = [{location: 1, path: '4'}, {location: 2, path: '4.1' }, {location: 3, path: '4.1.1'}, {location: 4, path: '4.1.2'}, {location: 5, path: '4.2'}, {location: 6, path: '4.2.1'}, {location: 7, path: '4.3'}, {location: 8, path: '4.3.1'}]

console.log (restructure (list))
.as-console-wrapper {min-height: 100% !important; top: 0}

使用索引意味着我们不必对任何内容进行排序;输入可以是任何顺序。

查找父对象包括将"4.3.1"替换为"4.3",然后在索引中查找。当我们尝试"4"时,它会查找空字符串,找不到它并使用根节点。

如果您喜欢使用正则表达式,则可以改用稍短的一行:

    const parent = index [path.replace (/(^|\.)[^.]+$/, '')] || root

但是,您可能还想在recent answer中研究类似问题的更为优雅的技术。我在这里的答案可以完成工作(有些丑陋的变化),但是该答案将教给您很多有关有效软件开发的知识。

答案 2 :(得分:1)

我很好奇,如果Scott的链接答案能够在不做任何修改的情况下解决此问题。可以!

import { tree } from './Tree'
import { bind } from './Func'

const parent = (path = "") =>
  bind
    ( (pos = path.lastIndexOf(".")) =>
        pos === -1
          ? null
          : path.substr(0, pos)
    )

const myTree =
  tree                          // <- make tree
    ( list                      // <- array of nodes
    , node => parent(node.path) // <- foreign key
    , (node, children) =>       // <- node reconstructor
        ({ ...node, children: children(node.path) }) // <- primary key
    )

console.log(JSON.stringify(myTree, null, 2))
[
  {
    "location": 1,
    "path": "4",
    "children": [
      {
        "location": 2,
        "path": "4.1",
        "children": [
          {
            "location": 3,
            "path": "4.1.1",
            "children": []
          },
          {
            "location": 4,
            "path": "4.1.2",
            "children": []
          }
        ]
      },
      {
        "location": 5,
        "path": "4.2",
        "children": [
          {
            "location": 6,
            "path": "4.2.1",
            "children": []
          }
        ]
      },
      {
        "location": 7,
        "path": "4.3",
        "children": [
          {
            "location": 8,
            "path": "4.3.1",
            "children": []
          }
        ]
      }
    ]
  }
]

Tree模块在​​this post中共享,下面是提供Func-

bind模块
// Func.js
const identity = x => x

const bind = (f, ...args) =>
  f(...args)

const raise = (msg = "") => // functional throw
  { throw Error(msg) }

// ...

export { identity, bind, raise, ... }

展开以下代码段,以在浏览器中验证结果-

// Func.js
const bind = (f, ...args) =>
  f(...args)
  
// Index.js
const empty = _ =>
  new Map

const update = (r, k, t) =>
  r.set(k, t(r.get(k)))

const append = (r, k, v) =>
  update(r, k, (all = []) => [...all, v])

const index = (all = [], indexer) =>
  all.reduce
      ( (r, v) => append(r, indexer(v), v)
      , empty()
      )
      
// Tree.js
// import { index } from './Index'
function tree (all, indexer, maker, root = null)
{ const cache =
    index(all, indexer)

  const many = (all = []) =>
    all.map(x => one(x))
    
  const one = (single) =>
    maker(single, next => many(cache.get(next)))

  return many(cache.get(root))
}

// Main.js
// import { tree } from './Tree'
// import { bind } from './Func'

const parent = (path = "") =>
  bind
    ( (pos = path.lastIndexOf(".")) =>
        pos === -1
          ? null
          : path.substr(0, pos)
    )

const list =
  [{location:1,path:'4'},{location:2,path:'4.1'},{location:3,path:'4.1.1'},{location:4,path:'4.1.2'},{location:5,path:'4.2'},{location:6,path:'4.2.1'},{location:7,path:'4.3'},{location:8,path:'4.3.1'}]

const myTree =
  tree
    ( list                      // <- array of nodes
    , node => parent(node.path) // <- foreign key
    , (node, children) =>       // <- node reconstructor
        ({ ...node, children: children(node.path) }) // <- primary key
    )

console.log(JSON.stringify(myTree, null, 2))

答案 3 :(得分:0)

的思维方式与Aadith相同,但来自迭代方法。我认为最有效的方法是使用链表结构,然后将其展平。

const list = [
  {
    location: 1,
    path: '4'
  },
  {
    location: 2,
    path: '4.1'
  },  
  {
    location: 3,
    path: '4.1.1'
  },  
  {
    location: 4,
    path: '4.1.2'
  },  
  {
    location: 5,
    path: '4.2'
  },  
  {
    location: 6,
    path: '4.2.1'
  },
  {
    location: 7,
    path: '4.3'
  },
  {
    location: 8,
    path: '4.3.1'
  }
];

let newList = [];
list.forEach((location) =>
{
  console.log('Handling location ',location);
  if(location.path.split('.').length==1)
  {
    location.children = [];
    newList.push(location);
  }
  else
  {
    newList.forEach(loc => {
      console.log('checking out: ',loc);
      let found = false;
      while(!found)
      {
        console.log(loc.path,'==',location.path.substring(0, location.path.lastIndexOf('.')));
        found = loc.path == location.path.substring(0, location.path.lastIndexOf('.'));
        if(!found)
        {
          for(let i=0;i<loc.children.length;i++)
          {
            let aloc = loc.children[i];

            found = aloc.path == location.path.substring(0, location.path.lastIndexOf('.'));
            if(found)
            {
              console.log('found it...', loc);
              location.children = [];
              aloc.children.push(location);
              break;
            }
          }

        }
        else
        {
          console.log('found it...', loc);
          location.children = [];
          loc.children.push(location);
        }
      }
    } );
  }
});

console.log(newList);

这是我快速又肮脏的解决方法

答案 4 :(得分:0)

感谢您的所有建议! 我绝对可以看到针对我的问题的非常复杂的解决方案。 到最后,我终于创建了自己的“脏”解决方案。

这绝对是一种较慢的方法,但是对于我的应用程序,此列表不会太长,以至于我应该太担心优化。

出于我的问题,我简化了扁平化的清单。尽管实际上,列表要复杂得多。我也可以通过我想找到它的孩子的路。

这是对我有用的解决方案。

function handleNested(list, location) {
  return list.map((item) => {
    if (item.children && item.children.length > 0) {
      item.children = handleNested(item.children, location);
    }
    if (
      location.path.startsWith(item.path) &&
      location.path.split(".").length === item.path.split(".").length + 1
    ) {
      return {
        ...item,
        children: item.children ? [...item.children, location] : [location],
      };
    } else {
      return item;
    }
  });
}

function locationList(path) {
  // Filtering the list to remove items with different parent path, and sorting by path depthness.
  const filteredList = list
    .filter((location) => location.path.startsWith(path))
    .sort((a, b) => a.path.length - b.path.length);

  let nestedList = [];
  // Looping through the filtered list and placing each item under its parent.
  for (let i = 0; i < filteredList.length; i++) {
    // Using a recursive function to find its parent.
    let res = handleNested(nestedList, filteredList[i]);
    nestedList = res;
  }

  return nestedList;
}

locationList("4");