按 parent 排序(拓扑排序)和数组元素的重要性

Sort by parent (topological sort) and importance of element on array

好吧,我有一个 objects 的数组,其中一些元素依赖于其他元素。

因此,我需要按重要性(parent 的依赖性)对其进行排序,以将其存储在数据库中并替换所有 children 的 parent 属性由各自 parent id.

数组示例:

[
    {
        "id": 1,
        "email": "a@b.com", // unique
        "parent": "c@b.com" // is nullable
    },
    {
        "id": 2,
        "email": "b@b.com",
        "parent": null
    },
    {
        "id": 3,
        "email": "c@b.com",
        "parent": "b@b.com"
    },
    {
        "id": 4,
        "email": "d@b.com",
        "parent": "a@b.com"
    },
    ...
]

依赖关系的图形示例:

预期结果: 按依赖项排序 (parent):

[
    {
        "id": 2,
        "email": "b@b.com",
        "parent": null
    },
    {
        "id": 3,
        "email": "c@b.com",
        "parent": 2
    },
    {
        "id": 1,
        "email": "a@b.com",
        "parent": 3 
    },
    {
        "id": 4,
        "email": "d@b.com",
        "parent": 1
    },
    ...
]

设置各自的 parent id 我正在使用(但它没有按 parent 级别排序:parent、children, 盛大children...):

let users = [
{
    "id": 1,
    "email": "a@b.com", // unique
    "parent": "c@b.com" // is nullable
},
{
    "id": 2,
    "email": "b@b.com",
    "parent": null
},
{
    "id": 3,
    "email": "c@b.com",
    "parent": "b@b.com"
},
{
    "id": 4,
    "email": "d@b.com",
    "parent": "a@b.com"
}
];

users = users.map(user => {
    user.parent = _.findIndex(users, i => user.parent === i.email);
    return user;
});

P.S: 在这种情况下,importance 概念指的是 parent 级别。 所以,首先我需要 parents,然后是 children、grandchildren,等等...

很抱歉,如果这个帖子解释得不好,如果您有疑问,我会寻找表达想法的最佳方式。

你可以使用递归函数

const data = [{
    "id": 1,
    "email": "a@b.com", // unique
    "parent": "c@b.com" // is nullable
  },
  {
    "id": 2,
    "email": "b@b.com",
    "parent": null
  },
  {
    "id": 3,
    "email": "c@b.com",
    "parent": "b@b.com"
  },
  {
    "id": 4,
    "email": "d@b.com",
    "parent": "a@b.com"
  },

]

const order = (arr, level) => {
  const children = arr.filter(e => e.parent === level); // get the elements that have the same parent ( level )
  const parent = arr.find(e => e.email === level); // get the parent
  
  return children.length 
    ? [
        ...children.map(e => 
          ({ ...e,
            parent: parent ? parent.id : null // update the parent to the id instead of email
          })),
        ...order(arr, children[0].email) // call the same function with the email of the first child of the current children array, it will become a parent
      ] 
    : children // otherwise return the array
}

const result = order(data, null)

console.log(result)

我将首先生成一个新输入,用 parent id 替换 parent email,并生成与它们所属的树相关的节点级别的新 属性。然后我们可以按 level 属性 对节点进行排序,在相等的 level 上,我们按 id.

排序

const input = [
    {"id": 1, "email": "a@b.com", "parent": "c@b.com"},
    {"id": 2, "email": "b@b.com", "parent": null},
    {"id": 3, "email": "c@b.com", "parent": "b@b.com"},
    {"id": 4, "email": "d@b.com", "parent": "a@b.com"},
    {"id": 5, "email": "x@b.com", "parent": "b@b.com"},
    {"id": 6, "email": "z@b.com", "parent": "x@b.com"},
    {"id": 7, "email": "y@b.com", "parent": null},
    {"id": 8, "email": "m@b.com", "parent": "y@b.com"}
];

const findParent = (mail) => input.find(x => x.email === mail);

const getLevel = (mail, lvl) =>
{    
    return mail ? getLevel(findParent(mail).parent, lvl + 1) : lvl;
}

let newInput = input.map(({id, email, parent}) =>
{
    return {
        id: id,
        email: email,
        parent: findParent(parent) ? findParent(parent).id : null,
        lvl: getLevel(parent, 0)
    };
});

let sortedInput = newInput.sort((a, b) =>
{
    return (a.lvl - b.lvl) ? a.lvl - b.lvl : a.id - b.id;
});

console.log(sortedInput);

下面是一种迭代方法(与提供的递归解决方案相对),您可以使用它来获得结果。基本上,首先找到根元素,然后遍历原始数组,查找以当前元素为父元素的元素。

要实现用 ID 替换父电子邮件,只需保留父名称到 ID 的映射:

var data = [{
  "id": 1,
  "email": "a@b.com", // unique
  "parent": "c@b.com" // is nullable
}, {
  "id": 2,
  "email": "b@b.com",
  "parent": null
}, {
  "id": 3,
  "email": "c@b.com",
  "parent": "b@b.com"
}, {
  "id": 4,
  "email": "d@b.com",
  "parent": "a@b.com"
}]

//Map email addresses to IDs
var map = data.reduce((accum, el) => {
  accum[el.email] = {
    id: el.id
  }
  return accum;
}, {});


var [root] = data.filter(el => !el.parent);
var users = [root];
var cur;
var children;
while (users.length < data.length) {
  cur = users[users.length - 1];
  //Find elments that have cur as parent
  children = data.filter(el => el.parent === cur.email);
  children.forEach(el => {
    users.push({
      id: el.id,
      email: el.email,
      parent: map[el.parent].id
    });
  });
}

console.log(users)

所有提供的答案都很好,但时间复杂度较慢 (O(n^2))。他们遍历所有节点,并为每个节点搜索其父节点(遍历节点 - O(n),并在每次迭代中寻找父节点 - O(n) * O(n) = O( n^2))

更好的解决方案是创建树结构并使用预排序 (DFS) 创建拓扑排序。

function createTree(nodesWithParentArray) {
    const initialTree = nodesWithParentArray.reduce(
      (acc, node) => {
        acc[node.id] = { data: node, children: [] }

        return acc
      },
      { null: { children: [] } }
    )

    const tree = nodesWithParentArray.reduce((acc, node) => {
      acc[node.parent].children.push(acc[node.id])

      return acc
    }, initialTree)

    return tree[null].children[0] // root
}

// test it like that:
createTree([{id:1, parent:2},{id:2, parent:null},{id:3, parent:2},{id:4, parent:3}])

上面的函数 return 一个嵌套的树结构,带有指向根节点的指针。剩下要做的是使用预序遍历来创建拓扑排序(O(n),因为我们只遍历每个节点一次):

function topologicalSort(tree) {
    const sortedList = []
    const queue = [treeRoot]

    while (queue.length) {
      const curNode = queue.shift()
      sortedList.push(curNode.data)
      queue.push(...curNode.children)
    }

    return sortedList
}

创建上面的树也是 O(n) 因为它只在输入数组上循环一次,所以最终的时间复杂度是 O(n) + O(n) => O(n)