【问题标题】:Sort by parent (topological sort) and importance of element on array按父元素排序(拓扑排序)和元素在数组中的重要性
【发布时间】:2019-01-18 16:25:00
【问题描述】:

嗯,我有一个包含对象的数组,其中一些元素依赖于其他元素。

因此,我需要按重要性(父级的依赖关系)对其进行排序,以将其存储在数据库中,并用各自的父级 id 替换所有子级的 parent 属性。

数组示例:

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

依赖的图示:

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

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

设置各自的父 id 我正在使用(但它没有按父级别排序:父、子、孙...):

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;
});

附注: 在这种情况下,importance 概念指的是parent 级别。 所以,首先我需要父母,然后是孩子、孙子等等……

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

【问题讨论】:

  • “重要性”是什么意思?哪个元素是“最重要的”?
  • @quirimmo 好问题,我刚刚为您的问题添加了答案。顺序是父母,孩子,孙子,...
  • 在您的示例中,parent 指的是父级的email 属性,但在您想要的输出中,它们切换到id。这是故意的吗? 编辑: 没关系,我看到你提到过这个:“用各自的 ID 替换孩子。”
  • @TylerRoper 在几秒钟内我说“所以,我需要按重要性(父级的依赖性)对其进行排序以存储在数据库中并用相应的 ID 替换子级。”其中各自的 Id 是父母
  • 你想要一个树遍历。您所描述的可能听起来像广度优先遍历或预排序深度优先遍历。您的样本太小,无法知道您要查找的样本。请使用这些术语进行搜索。

标签: javascript arrays sorting lodash topological-sort


【解决方案1】:

我将首先生成一个新输入,将parent email 替换为parent id 以及与它们所属的树相关的节点级别的新属性。然后我们可以通过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);

【讨论】:

  • 这看起来像我想要的,但它没有按父级别(父、子、孙...)对元素进行排序:请参见此处:jsbin.com/rumolulige
  • 哦,那么您是否想要先没有父元素的元素(可能按 id 排序),然后是一级节点、二级节点等等......(如果我们认为这是一棵树) ?
  • 非常感谢,这项工作!我尝试使用 2.5MM 元素 ;)
【解决方案2】:

你可以使用递归函数

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)

【讨论】:

    【解决方案3】:

    以下是可用于实现结果的迭代方法(与提供的递归解决方案相反)。基本上,首先找到根元素,然后遍历原始数组,寻找以当前元素为父元素的元素。

    要实现用 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)

    【讨论】:

      【解决方案4】:

      所有提供的答案都很好,但时间复杂度较慢 (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}])
      

      上面的函数会返回一个嵌套的树结构,带有一个指向根节点的指针。剩下要做的是使用前序遍历来创建拓扑排序(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)

      【讨论】:

      • 我认为在最坏的情况下这不是 O(N)。 Array.shift 的复杂度为 O(M),其中 M 是数组的长度。因此,在所有节点都是根的子节点的最坏情况下,您有 N 个平均长度为 N/2 的 shift 操作,使得这个 O(N^2)。
      • 如果您对Array.shift 的看法是正确的,则可以很容易地用其他一些技术替换它(例如带有指向第一个相关元素的指针的数组)
      猜你喜欢
      • 2012-12-26
      • 2019-09-01
      • 2011-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-16
      • 1970-01-01
      相关资源
      最近更新 更多