public List<Group> buildGroupsDirected() {
    List<Group> groups = new LinkedList<>();

    // First determine 'start' relations, i.e. relations without incoming
    // edges in the relation graph
    TLongSet starts = new TLongHashSet();
    Collection<Long> ids = graph.getNodes();
    for (long id : ids) {
      if (graph.getEdgesIn(id).isEmpty()) {
        starts.add(id);
      }
    }
    // Build sub-graphs reachable from 'start' relations
    logger.debug("Number of start relations: " + starts.size());
    for (long start : starts.toArray()) {
      groups.add(build(start));
    }

    // In case of circles within the relation graph that are not reachable
    // from any start relation, there may be some relations left, that have
    // not been put into groups yet.
    TLongSet remaining = new TLongHashSet();
    remaining.addAll(ids);
    for (Group group : groups) {
      remaining.removeAll(group.getRelationIds());
    }
    if (remaining.size() > 0) {
      logger.debug("remaining: " + remaining.size());
      while (!remaining.isEmpty()) {
        long id = any(remaining);

        TLongSet reachable = reachable(graph, id);
        remaining.removeAll(reachable);

        long lowest = IdUtil.lowestId(reachable);
        groups.add(new Group(lowest, reachable));
      }
    }

    return groups;
  }
  public List<Group> buildGroupsUndirected() {
    List<Group> groups = new LinkedList<>();

    TLongSet nodes = new TLongHashSet(graph.getNodes());
    while (!nodes.isEmpty()) {
      long id = any(nodes);

      TLongSet reachable = reachable(graph, id);
      nodes.removeAll(reachable);

      groups.add(new Group(id, reachable));
    }
    return groups;
  }