【问题标题】:javascript grouping linked nodesjavascript分组链接节点
【发布时间】:2017-05-04 21:05:17
【问题描述】:

我有 2 个列表 - 节点和链接...现在我想要的是将所有 直接/间接 链接的元素添加到不同组中的最有效方法...例如, 0 连接到 1 连接到 2 所以节点 0,1,2 成为组 1.... 节点 3 连接到 4 所以它成为组 2 等等....提前感谢您的帮助:)这是正在执行的 d3 实现的一部分。

PS:这些列表很容易出现在数以万计的节点和链接中。

"nodes":[
   {
      "id":0,
      "x":1509.9862,
      "y":-609.1013
   },
   {
      "id":1,
      "x":1645.9578,
      "y":-85.06705
   },
   {
      "id":2,
      "x":1948.1533,
      "y":-469.3646
   },
   {
      "id":3,
      "x":348.1533,
      "y":-669.3646
   },
   {
      "id":4,
      "x":1448.1533,
      "y":-1469.3646
   }
   ...
  ]

  "links":[
     {
        "from":0,
        "to":1
     },
     {
        "from":1,
        "to":2
     },
     {
        "from":3,
        "to":4
     }
     ...
  ]  

【问题讨论】:

    标签: javascript arrays list d3.js grouping


    【解决方案1】:

    这是一个经典的 UnionFind 问题。这个想法是将每个节点视为一个具有指向其父节点的指针的集合。具有相同父亲的节点在同一组中。所以对于你的问题,我们可以在一开始就创建 n 个集合。然后遍历链接以将通过同一链接连接的每个人分组。复杂度为 O(n),其中 n 是节点数。

    nodes = [{
        "id": 0,
        "x": 1509.9862,
        "y": -609.1013
      },
      {
        "id": 1,
        "x": 1645.9578,
        "y": -85.06705
      },
      {
        "id": 2,
        "x": 1948.1533,
        "y": -469.3646
      },
      {
        "id": 3,
        "x": 348.1533,
        "y": -669.3646
      },
      {
        "id": 4,
        "x": 1448.1533,
        "y": -1469.3646
      }
    ];
    
    links = [{
        "from": 0,
        "to": 1
      },
      {
        "from": 1,
        "to": 2
      },
      {
        "from": 3,
        "to": 4
      }
    ];
    
    // union-find is a data structure that can union two sets and check
    // whether two element in the same set.
    
    var father = {};
    
    function group(nodes, links) {
      // create n set with each set has the node as its only element
      nodes.forEach(function(node, i) {
        father[node.id] = node.id;
      });
    
      // union each set that has a link between them
      links.forEach(function(link, i) {
        union(link.from, link.to);
      });
    
      // for each unioned set, group nodes together
      var id = 1;
      var groupIdCnt = {};
      var groupIds = {};
      nodes.forEach(function(node, i) {
        var f = find(node.id);
        if (typeof groupIds[f] === 'undefined') {
          groupIds[f] = id;
          groupIdCnt[id] = 1;
          id++;
        } else {
          groupIdCnt[groupIds[f]]++;
        }
      });
    
      var groups = {};
      nodes.forEach(function(node, i) {
        var f = find(node.id);
        if (groupIdCnt[groupIds[f]] === 1) {
          node['group'] = 0;
        } else {
          node['group'] = groupIds[f];
        }
    
        if (typeof groups[node['group']] === 'undefined') {
          groups[node['group']] = [];
        }
        groups[node['group']].push(node);
      });
    
      return Object.values(groups);
    
    }
    
    // find father of each set
    function find(node) {
      // if it is the root, return
      if (father[node] === node) {
        return node;
      }
      // if not, find the father and point to it
      father[node] = find(father[node]);
      return father[node];
    }
    
    // update the father of set which includes node1 to the father of set which includes node 2
    function union(node1, node2) {
      father[find(node1)] = find(node2);
    }
    
    // O(n), since we visit each node once
    var groups = group(nodes, links);
    console.log(nodes);
    console.log(groups);

    【讨论】:

    • 这太棒了@junbang-huang...我需要一些不同的东西...如果每个节点对象都可以有一个新的attr“组”:1,而不是加入列表, "group":2 等。不知道是容易还是难
    • 让我试一试
    • @ideate 这应该返回你想要的。
    • 感谢您的解决方案 :) 完美运行...现在将对数千个节点和链接进行压力测试...
    • 让我知道结果。我还没有对此进行测试
    【解决方案2】:

    此代码吐出一个对象,其键是节点 ID,其值是组 ID,不一定是顺序的。

    var obj = {
    "links":[
         {
            "from":0,
            "to":1
         },
         {
            "from":1,
            "to":2
         },
         {
            "from":5,
            "to":4
         },
         {
            "from":3,
            "to":4
         }
      ]  
    };
    
    var groups = {};
    var nextGrp = 1;
    
    for (var i=0, l; l = obj.links[i]; i++) {
      if (groups[l.from]) {
        if (groups[l.to]) {
          if (groups[l.to] != groups[l.from]) {
            // the two items span two different groups which must now be joined into 1
            for (var j in groups) {
              if (groups[j] == groups[l.to]) {
                groups[j] = groups[l.from];
              }
            }
          }
        } else {
          groups[l.to] = groups[l.from];
        }
      } else if (groups[l.to]) {
        groups[l.from] = groups[l.to];
      } else {
        groups[l.from] = nextGrp;
        groups[l.to] = nextGrp;
        nextGrp++;
      }
    }
    
    console.log(groups);

    【讨论】:

    • 非常感谢@james 的解决方案
    【解决方案3】:

    在下面的解决方案中,我创建了 links 组,它们相互关联。我通过循环遍历所有from/to 组合来做到这一点,并找出是否已经将其中任何一个添加到links 的任何累积组中。如果他们有,那么我只需将 fromto 值添加(或重新添加)到该组。如果尚未对 fromto 值进行分组,则我创建一个新组并将 fromto 值添加到其中。请注意,这些“组”实际上是 Javascript 集,一种新的 ES6/ES2015 数据类型,可以更轻松地处理不需要和/或不允许重复的元素“组”。

    一旦建立了组/链接集,我只需向每个node 添加一个属性,指示它属于links 的哪个组。

    请注意,为了这个演示代码,我已经简化/整理了一些 node 值。我还添加了一些额外的链接,只是为了演示一些需要处理的进一步案例。

    const groupNodes = (nodes, links) => {
      const groups = links.reduce((grps, {from, to}) => {
        if (!grps.some(grp => {
          if (grp.has(from) || grp.has(to)) return grp.add(from).add(to);
        })) grps.push(new Set([from, to]));
        return grps;
      }, []);
      nodes.forEach(node => {
        groups.forEach((grp, i) => { if (grp.has(node.id)) node.group = i; });
      });
      return nodes;
    };
    
    
    
    const nodes = [
      {
        "id":0,
        "x":0,
        "y":0
      },
      {
        "id":1,
        "x":11,
        "y":-11
      },
      {
        "id":2,
        "x":22,
        "y":-22
      },
      {
        "id":3,
        "x":33,
        "y":-33
      },
      {
        "id":4,
        "x":44,
        "y":-44
      },
      {
        "id":5,
        "x":55,
        "y":-55
      },
      {
        "id":6,
        "x":66,
        "y":-66
      }
    ];
    const links = [
      {
        "from": 0,
        "to"  : 1
      },
      {
        "from": 1,
        "to"  : 2
      },
      {
        "from": 2,
        "to"  : 0
      },
      {
        "from": 3,
        "to"  : 4
      },
      {
        "from": 4,
        "to"  : 5
      },
      {
        "from": 6,
        "to"  : 0
      }
    ];
    
    console.log(JSON.stringify(groupNodes(nodes, links)));

    【讨论】:

    • 它在某种程度上给了我想要的东西......但是非常感谢您的解决方案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-10
    • 1970-01-01
    • 2014-05-07
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    相关资源
    最近更新 更多