javascript 从关系对象(可能是循环关系)创建“树”或目录结构

du7egjpx  于 2023-06-20  发布在  Java
关注(0)|答案(3)|浏览(93)

给定一个对象,其键是项目,值是依赖项。我一直在尝试组装一个嵌套的树结构,没有任何表示的循环依赖关系(想想文件和文件夹)。
下面是一个简单的例子:

{
  'a': ['b'],
  'b': ['c'],
  'c': [],
}

这将产生:

{
  'a': {
    'b': {
      'c': {}
    }
  }
}

以下是“规则”:
1.任何未被其他依赖项使用的依赖项都应位于树的顶部
1.任何只有一次使用的依赖项都应该嵌套在该依赖项中,依此类推

  1. 2个或更多依赖项使用的任何依赖项都应该作为最高节点的兄弟节点放置
    下面是一个更复杂的例子:
{
  'a': ['b', 'q'],
  'b': ['c', 'f'],
  'c': ['d'],
  'p': ['o'],
  'o': [],
  "d": [],
  'e': ['c'],
  "q": []
}

应该是:

{
  'a': {
    'b': 
      "f": {},
    },
    'q': {},
  },
  "p": {
    'o': {},
  },
  "c": { // note this would first get put ito b, but because its used by e it becomes a sibling of the highest node
    "d"; {}
  },
  "e": {}
}

我已经尝试了很多解决方案,从使用嵌套的点键到在class Node示例上使用full,因为我一直遇到最大调用堆栈问题。
我也被卡住了,因为建立一个节点的性质,让它重新定位在树的其他地方。
我知道这是一个非常开放的问题,但我想知道是否有一个更简单的解决方案或思考它的方法。

kxeu7u2r

kxeu7u2r1#

尝试创建一个图,这看起来像是一个两部分的解决方案,因为你可能有循环依赖关系,直到整个对象被解析,你才知道。创建图形后,可以开始递归弹出节点并将它们添加到新对象。

72qzrwbm

72qzrwbm2#

你有

digraph {
    a -> b  a -> q
    b -> c [color=red]  b -> f
    c -> d
    p -> o
    d
    e -> c [color=red]
    q
    // Z->a Z->c Z->e Z->p
}

首先删除重复连接(红色)
第二,构建图表
第三,将所有无父节点添加到根节点
https://tsplay.dev/m3v71N

// convert to edge array for simplicity
const edges = Object.entries(data).flatMap(([k, v]) => v.map(e => [k, e]))
console.log(edges)

// list all nodes
let nodes = Object.entries(data).flat(2)
nodes = Array.from(new Set(nodes)).sort()
console.log(nodes)

// filter out red edges
const goodEdges = edges.filter((a) => edges.filter(b => b[1] === a[1]).length === 1)
console.log(goodEdges)

// add root edges to missing node
nodes.map(e => goodEdges.find(a => a[1] === e) || goodEdges.push(['$', e]))
console.log(goodEdges)

// create nodes
let nodeInstances = Object.fromEntries(nodes.map(e => [e, {} as Record<string, any>]))
nodeInstances.$ = {};
console.log(nodeInstances)

// build tree
goodEdges.map(e => nodeInstances[e[0]][e[1]] = nodeInstances[e[1]])

// return tree
console.log(nodeInstances.$)
pqwbnv8z

pqwbnv8z3#

首先,我们来看看有多个父母的孩子:将所有数组成员收集到一个数组中,并标识其中的重复项。
然后确定结果中应该是顶层关键字的关键字:这些是不作为子项出现的键,或者是它们之间的副本。
然后,您可以使用深度优先遍历,并为每个访问的键创建一个节点对象。不要为重复项创建节点条目,除非在顶层。

const exclude = (arr, omitSet) => arr.filter(val => !omitSet.has(val));

const duplicateSet = (arr) =>
    new Set(arr.filter(function (val) {
        return !this.delete(val);
    }, new Set(arr)));

function toNested(data) {
    const nodes = {};
    const descendants = Object.values(data).flat();
    const withMultipleParents = duplicateSet(descendants);
    const withSingleParents = new Set(exclude(descendants, withMultipleParents));
    const withNoParents = [...exclude(Object.keys(data), withSingleParents), ...withMultipleParents];
    
    function dfs(key) {
        if (nodes[key]) return nodes[key];
        nodes[key] = {};
        for (const child of data[key] ?? []) {
            if (withSingleParents.has(child)) nodes[key][child] = dfs(child);
        }
        return nodes[key];
    }

    return Object.fromEntries(withNoParents.map(key => [key, dfs(key)]));
}

const data = {'a': ['b', 'q'],'b': ['c', 'f'],'c': ['d'],'p': ['o'],'o': [],"d": [],'e': ['c'],"q": []};

console.log(toNested(data));

相关问题