com.mantledillusion.essentials.graph.NodeEssentials Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graph-essentials Show documentation
Show all versions of graph-essentials Show documentation
Graph Essentials contain utility classes useful for graph visualization purposes.
The newest version!
package com.mantledillusion.essentials.graph;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class NodeEssentials {
private static class Cluster {
private final NodeId id;
private final Set> siblingIds;
private final int minSize;
private final int maxSize;
private Cluster(NodeId id, Set> siblingIds, int minSize, int maxSize) {
this.id = id;
this.siblingIds = siblingIds;
this.minSize = minSize;
this.maxSize = maxSize;
}
private NodeId getId() {
return this.id;
}
private boolean clusterableWith() {
return !this.siblingIds.isEmpty() && this.maxSize > this.siblingIds.size();
}
private int getScore() {
return this.siblingIds.size() - this.minSize;
}
private boolean clusterableWith(Cluster siblingId) {
int size = this.id.getClusterSize() + siblingId.id.getClusterSize();
return this.siblingIds.contains(siblingId.getId()) && this.maxSize >= size;
}
private void addSibling(NodeId siblingId) {
this.siblingIds.add(siblingId);
}
@SafeVarargs
private final void removeSiblings(Cluster... siblings) {
this.siblingIds.removeAll(Arrays.stream(siblings).map(Cluster::getId).collect(Collectors.toSet()));
}
@Override
public String toString() {
return this.id.toString() + ' ' + this.siblingIds;
}
}
private static final Comparator> COMP_SCORE_ASC = Comparator.comparingInt(Cluster::getScore);
private static final Comparator> COMP_SCORE_DESC = COMP_SCORE_ASC.reversed();
static , IdType> Map, NodeType> registerNodes(Collection> nodes) {
return nodes.stream().collect(Collectors.toMap(registration -> new NodeId<>(registration.getIdentifier()), Node.Registration::getNode));
}
static , IdType> Map, Set>> registerNeighbors(Map, NodeType> nodeRegistry,
Collection> nodes) {
Map, Set>> neighborRegistry = new HashMap<>();
nodes.forEach(registration -> {
NodeId nodeId = new NodeId<>(registration.getIdentifier());
Set neighbors = registration.getNeighbors();
if (neighbors == null || neighbors.isEmpty()) {
neighborRegistry.computeIfAbsent(nodeId, id -> new HashSet<>());
} else {
for (IdType neighbor : neighbors) {
NodeId neighborId = new NodeId<>(neighbor);
if (!nodeRegistry.containsKey(neighborId)) {
throw new IllegalArgumentException(String.format("The neighbor %s of node %s is unknown",
neighbor, nodeId));
}
// REGISTER NODE -> NEIGHBOR
neighborRegistry
.computeIfAbsent(nodeId, id -> new HashSet<>())
.add(neighborId);
// REGISTER NEIGHBOR -> NODE
neighborRegistry
.computeIfAbsent(neighborId, id -> new HashSet<>())
.add(nodeId);
}
}
});
return neighborRegistry;
}
static void determineDepths(Map, Set>> neighborRegistry,
Map, Integer> depthRegistry,
NodeId currentNode,
Integer depth) {
// ITERATE CURRENT NODE'S NEIGHBORS
for (NodeId neighbor: neighborRegistry.get(currentNode)) {
// DETERMINE WHETHER THE NEIGHBORS DEPTH HAS NOT BEEN DETERMINED,
// OR ITS DEPTH FROM THE CURRENT NODE IS SHALLOWER THAN DETERMINED PREVIOUSLY
if (!depthRegistry.containsKey(neighbor) || depthRegistry.get(neighbor) > depth) {
depthRegistry.put(neighbor, depth);
determineDepths(neighborRegistry, depthRegistry, currentNode, depth + 1);
}
}
}
static , IdType> Set> clusterSiblings(Map, NodeType> nodeRegistry,
Map, Set>> neighborRegistry,
NodeId currentNode,
Set> excluded) {
// DETERMINE ALL NEIGHBORS THAT HAVE TO BE CONTAINED BY EXACTLY ONE CLUSTER ...
Set> neighbors = new HashSet<>(neighborRegistry.get(currentNode));
// ... BUT WHICH ARE NOT EXPLICITLY EXCLUDED
neighbors.removeAll(excluded);
// REGISTER EACH NEIGHBOR AS A POTENTIAL CLUSTER
Map, Cluster> clusterRegistry = new HashMap<>();
neighbors.forEach(neighborId -> clusterRegistry.put(neighborId, new Cluster<>(
neighborId, collectSiblings(nodeRegistry, neighbors, neighborId),
nodeRegistry.get(neighborId).getMinClusterSize(), nodeRegistry.get(neighborId).getMaxClusterSize()))
);
// LOOP CLUSTERING
clusterLoop: while (true) {
// ITERATE OVER ALL CURRENTLY KNOWN CLUSTERS
List> neighborQueue = clusterRegistry.values().stream()
// FILTER FOR SUCH CLUSTERS THAT ARE GENERALLY ABLE TO MERGE WITH OTHER CLUSTERS
.filter(Cluster::clusterableWith)
// SORT CLUSTERS BY THEIR SCORE
.sorted(COMP_SCORE_DESC)
// COLLECT THEM IN ORDER
.collect(Collectors.toList());
// ITERATE OVER ORDERED NEIGHBORS FOR A NEIGHBOR TO MERGE
for (Cluster neighbor: neighborQueue) {
// ITERATE OVER ORDERED NEIGHBORS FOR A SIBLING TO MERGE WITH
for (Cluster sibling: neighborQueue) {
// CHECK IF NEIGHBOR AND SIBLING CAN CLUSTER WITH EACH OTHER
if (neighbor != sibling && neighbor.clusterableWith(sibling) && sibling.clusterableWith(neighbor)) {
// CLUSTER IDS
NodeId clusterId = neighbor.getId().clusterWith(sibling.getId());
// CLUSTER NODES
NodeType clusterNode = nodeRegistry.get(neighbor.getId()).clusterWith(nodeRegistry.get(sibling.getId()));
// REMOVE THE CLUSTERED NEIGHBOR FROM NODE REGISTRY
nodeRegistry.remove(neighbor.getId());
// REMOVE THE CLUSTERED NEIGHBOR FROM NODE REGISTRY
nodeRegistry.remove(sibling.getId());
// ADD THE CREATED CLUSTER TO NODE REGISTRY
nodeRegistry.put(clusterId, clusterNode);
// REMOVE AND ITERATE ALL NEIGHBORS OF THE CLUSTERED NEIGHBOR
Set> clusterNeighbors = new HashSet<>();
neighborRegistry.remove(neighbor.getId()).stream()
.filter(other -> !Objects.equals(other, sibling.getId()))
// REPLACE THE CLUSTERED NEIGHBOR OF SUCH NEIGHBORS ...
.peek(other -> neighborRegistry.get(other).remove(neighbor.getId()))
// ... WITH THE CREATED CLUSTER IN THE NEIGHBOR REGISTRY ...
.peek(other -> neighborRegistry.get(other).add(clusterId))
// ... AND REGISTER IT AS A NEIGHBOR TO THE CREATED CLUSTER
.forEach(clusterNeighbors::add);
// REMOVE AND ITERATE ALL NEIGHBORS OF THE CLUSTERED SIBLING
neighborRegistry.remove(sibling.getId()).stream()
.filter(other -> !Objects.equals(other, neighbor.getId()))
// REPLACE THE CLUSTERED SIBLING OF SUCH NEIGHBORS ...
.peek(other -> neighborRegistry.get(other).remove(sibling.getId()))
// ... WITH THE CREATED CLUSTER IN THE NEIGHBOR REGISTRY ...
.peek(other -> neighborRegistry.get(other).add(clusterId))
// ... AND REGISTER IT AS A NEIGHBOR TO THE CREATED CLUSTER
.forEach(clusterNeighbors::add);
// REGISTER NEIGHBORS OF THE CREATED CLUSTER IN THE NEIGHBOR REGISTRY
neighborRegistry.put(clusterId, clusterNeighbors);
// REMOVE THE CLUSTERED NEIGHBOR FROM NODE REGISTRY
clusterRegistry.remove(neighbor.getId());
// REMOVE THE CLUSTERED SIBLING FROM NODE REGISTRY
clusterRegistry.remove(sibling.getId());
// ITERATE OVER ALL CLUSTERS
clusterRegistry.values().stream()
// REMOVE CLUSTERED NEIGHBOR AND SIBLING FROM THEIR SIBLINGS
.peek(other -> other.removeSiblings(neighbor, sibling))
// FILTER FOR SUCH CLUSTERS THAT CAN BE CLUSTERED WITH THE CREATED CLUSTER
.filter(other -> nodeRegistry.get(other.getId()).clusterableWith(clusterNode) == Node.Clusterability.SIBLINGS)
// ADD THE CREATED CLUSTER AS A SIBLING
.forEach(other -> other.addSibling(clusterId));
// RETRIEVE ALL SIBLINGS FOR THE CREATED CLUSTER
Set> others = collectSiblings(nodeRegistry, clusterRegistry.keySet(), clusterId);
// REGISTER THE CREATED CLUSTER IN THE CLUSTER REGISTRY
clusterRegistry.put(clusterId, new Cluster<>(clusterId, others,
clusterNode.getMinClusterSize(), clusterNode.getMaxClusterSize()));
// RESTART CLUSTERING AS NEIGHBORS HAVE CHANGED
continue clusterLoop;
}
}
}
//
break;
}
// RETURN ALL CLUSTERS
return clusterRegistry.keySet();
}
private static , IdType> Set> collectSiblings(Map, NodeType> nodeRegistry,
Set> neighbors,
NodeId neighborId) {
// ITERATE ALL OTHER NEIGHBORS ...
return neighbors.stream()
// ... WHICH ARE NOT THE NEIGHBOR ITSELF, BUT ...
.filter(other -> !Objects.equals(other, neighborId))
// ... WITH WHICH THE NEIGHBOR ALLOWS TO BE CLUSTERED
.filter(other -> nodeRegistry.get(neighborId).clusterableWith(nodeRegistry.get(other)) == Node.Clusterability.SIBLINGS)
// COLLECT THEM
.collect(Collectors.toSet());
}
static Set union(Set these, Set those) {
return Stream.concat(these.stream(), those.stream()).collect(Collectors.toSet());
}
}