Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit e694ba7

Browse files
refactor: unify DiffResult to include edit distance and degree changes
GraphDiffAnalyzer had three independently-computed views of the same diff: computeDiff() for node/edge sets, computeEditDistance() for edit distance (which internally called computeDiff() again), and findDegreeChanges() for degree deltas (which independently computed common nodes and iterated degrees). A user needing all three had to pay for three separate passes over the same graph data. DiffResult now computes edit distance and degree changes in the single computeDiff() pass. The edit distance is the sum of added/removed nodes and edges. Degree changes are tracked for common nodes only (nodes in both graphs whose degree differs). Both computeEditDistance() and findDegreeChanges() are preserved as convenience methods that delegate to computeDiff(), maintaining full backward compatibility. Javadoc updated to recommend using computeDiff().getEditDistance() / getDegreeChanges() directly when multiple metrics are needed. DiffResult.getSummary() now includes edit distance and degree change count. All collections remain unmodifiable (DiffResult is immutable). Also adds 46 comprehensive tests covering: - Constructor validation (null guards) - Empty, identical, and disjoint graphs - Partial overlap (added/removed/rewired edges) - Jaccard similarity edge cases - Edit distance computation - Degree change detection - EdgeDiff normalization and equality - DiffResult summary content - Larger scenarios (star growth, path-to-triangle) - DiffResult immutability (unmodifiable collections)
1 parent a1ccef2 commit e694ba7

2 files changed

Lines changed: 612 additions & 78 deletions

File tree

Gvisual/src/gvisual/GraphDiffAnalyzer.java

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public GraphDiffAnalyzer(Graph<String, edge> graphA, Graph<String, edge> graphB)
5050

5151
/**
5252
* Holds the complete diff result between two graphs.
53+
* <p>
54+
* Includes node/edge differences, similarity metrics, edit distance,
55+
* and degree changes — all computed in a single pass by
56+
* {@link GraphDiffAnalyzer#computeDiff()}.
5357
*/
5458
public static class DiffResult {
5559
private final Set<String> addedNodes;
@@ -60,11 +64,14 @@ public static class DiffResult {
6064
private final List<EdgeDiff> commonEdges;
6165
private final double nodeJaccard;
6266
private final double edgeJaccard;
67+
private final int editDistance;
68+
private final Map<String, int[]> degreeChanges;
6369

6470
public DiffResult(Set<String> addedNodes, Set<String> removedNodes,
6571
Set<String> commonNodes, List<EdgeDiff> addedEdges,
6672
List<EdgeDiff> removedEdges, List<EdgeDiff> commonEdges,
67-
double nodeJaccard, double edgeJaccard) {
73+
double nodeJaccard, double edgeJaccard,
74+
int editDistance, Map<String, int[]> degreeChanges) {
6875
this.addedNodes = Collections.unmodifiableSet(addedNodes);
6976
this.removedNodes = Collections.unmodifiableSet(removedNodes);
7077
this.commonNodes = Collections.unmodifiableSet(commonNodes);
@@ -73,6 +80,8 @@ public DiffResult(Set<String> addedNodes, Set<String> removedNodes,
7380
this.commonEdges = Collections.unmodifiableList(commonEdges);
7481
this.nodeJaccard = nodeJaccard;
7582
this.edgeJaccard = edgeJaccard;
83+
this.editDistance = editDistance;
84+
this.degreeChanges = Collections.unmodifiableMap(degreeChanges);
7685
}
7786

7887
/** Nodes present in B but not in A. */
@@ -99,6 +108,19 @@ public DiffResult(Set<String> addedNodes, Set<String> removedNodes,
99108
/** Jaccard similarity of edge sets: |A∩B| / |A∪B|. */
100109
public double getEdgeJaccard() { return edgeJaccard; }
101110

111+
/**
112+
* Graph edit distance: total additions + removals (nodes and edges)
113+
* needed to transform A into B.
114+
*/
115+
public int getEditDistance() { return editDistance; }
116+
117+
/**
118+
* Nodes whose degree changed between graphs A and B.
119+
* Keys are node IDs; values are {@code [degreeInA, degreeInB]}.
120+
* Only includes nodes present in both graphs.
121+
*/
122+
public Map<String, int[]> getDegreeChanges() { return degreeChanges; }
123+
102124
/** True if the two graphs are structurally identical. */
103125
public boolean isIdentical() {
104126
return addedNodes.isEmpty() && removedNodes.isEmpty()
@@ -115,6 +137,10 @@ public String getSummary() {
115137
addedNodes.size(), removedNodes.size(), commonNodes.size(), nodeJaccard));
116138
sb.append(String.format("Edges: %d added, %d removed, %d common (Jaccard: %.4f)%n",
117139
addedEdges.size(), removedEdges.size(), commonEdges.size(), edgeJaccard));
140+
sb.append(String.format("Edit distance: %d%n", editDistance));
141+
if (!degreeChanges.isEmpty()) {
142+
sb.append(String.format("Degree changes: %d nodes%n", degreeChanges.size()));
143+
}
118144
if (isIdentical()) {
119145
sb.append("Graphs are structurally identical.\n");
120146
}
@@ -169,6 +195,12 @@ public String toString() {
169195

170196
/**
171197
* Compute the full diff between graphA and graphB.
198+
* <p>
199+
* This single call computes all node/edge differences, Jaccard
200+
* similarity, edit distance, and degree changes. Use the returned
201+
* {@link DiffResult} to access everything — there is no need to call
202+
* {@link #findDegreeChanges()} or {@link #computeEditDistance()}
203+
* separately unless you specifically want to avoid the full diff.
172204
*
173205
* @return a DiffResult with all differences and similarity scores
174206
*/
@@ -200,45 +232,55 @@ public DiffResult computeDiff() {
200232
double nodeJaccard = jaccard(nodesA.size(), nodesB.size(), commonNodes.size());
201233
double edgeJaccard = jaccard(edgesA.size(), edgesB.size(), commonEdgeSet.size());
202234

235+
// Edit distance: total structural changes to transform A into B
236+
int editDistance = addedNodes.size() + removedNodes.size()
237+
+ addedEdgeSet.size() + removedEdgeSet.size();
238+
239+
// Degree changes for common nodes
240+
Map<String, int[]> degreeChanges = new TreeMap<>();
241+
for (String node : commonNodes) {
242+
int degA = graphA.degree(node);
243+
int degB = graphB.degree(node);
244+
if (degA != degB) {
245+
degreeChanges.put(node, new int[]{degA, degB});
246+
}
247+
}
248+
203249
return new DiffResult(
204250
addedNodes, removedNodes, commonNodes,
205251
new ArrayList<>(addedEdgeSet),
206252
new ArrayList<>(removedEdgeSet),
207253
new ArrayList<>(commonEdgeSet),
208-
nodeJaccard, edgeJaccard
254+
nodeJaccard, edgeJaccard,
255+
editDistance, degreeChanges
209256
);
210257
}
211258

212259
/**
213260
* Find nodes that exist in both graphs but whose degree changed.
261+
* <p>
262+
* Convenience method — delegates to {@link #computeDiff()} internally.
263+
* Prefer calling {@code computeDiff().getDegreeChanges()} if you also
264+
* need other diff information, to avoid redundant work.
214265
*
215266
* @return map of node to [degreeInA, degreeInB] for changed nodes
216267
*/
217268
public Map<String, int[]> findDegreeChanges() {
218-
Set<String> common = new HashSet<>(graphA.getVertices());
219-
common.retainAll(graphB.getVertices());
220-
221-
Map<String, int[]> changes = new TreeMap<>();
222-
for (String node : common) {
223-
int degA = graphA.degree(node);
224-
int degB = graphB.degree(node);
225-
if (degA != degB) {
226-
changes.put(node, new int[]{degA, degB});
227-
}
228-
}
229-
return changes;
269+
return computeDiff().getDegreeChanges();
230270
}
231271

232272
/**
233273
* Compute the edit distance: total node/edge additions + removals
234274
* needed to transform A into B.
275+
* <p>
276+
* Convenience method — delegates to {@link #computeDiff()} internally.
277+
* Prefer calling {@code computeDiff().getEditDistance()} if you also
278+
* need other diff information, to avoid redundant work.
235279
*
236280
* @return the graph edit distance
237281
*/
238282
public int computeEditDistance() {
239-
DiffResult diff = computeDiff();
240-
return diff.getAddedNodes().size() + diff.getRemovedNodes().size()
241-
+ diff.getAddedEdges().size() + diff.getRemovedEdges().size();
283+
return computeDiff().getEditDistance();
242284
}
243285

244286
// ── Helpers ─────────────────────────────────────────────────

0 commit comments

Comments
 (0)