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

Commit ae1e8ce

Browse files
perf(distance): consolidate generateReport from 8+ O(V²) passes to single pass
The generateReport() method previously made 8+ separate iterations over the V² distance matrix (getAveragePathLength, getWienerIndex, getHarmonicMeanDistance, getDistanceHistogram, getSeparationRatio, and 3x getDistancePercentile each calling collectFiniteDistances+sort). Consolidated into a single O(V²) traversal that computes all aggregate metrics (sum, reciprocal sum, histogram, reachable count) simultaneously, then sorts the collected distances once for all percentile lookups. Also replaced Collections.nCopies+join bar rendering with direct StringBuilder append to avoid temporary List allocation per histogram row.
1 parent f6b1f4f commit ae1e8ce

1 file changed

Lines changed: 70 additions & 14 deletions

File tree

Gvisual/src/gvisual/GraphDistanceDistribution.java

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -294,33 +294,75 @@ public int getDistinctDistanceCount() {
294294
/**
295295
* Generates a human-readable text report of the distance distribution.
296296
*
297+
* <p><b>Performance:</b> Computes all aggregate statistics (average path
298+
* length, Wiener index, harmonic mean, histogram, separation ratio, and
299+
* percentiles) in a single O(V²) pass over the distance matrix, then
300+
* sorts the collected distances once for all percentile queries. The
301+
* previous implementation made 8+ separate O(V²) passes and sorted the
302+
* distance list 3 times independently.</p>
303+
*
297304
* @return multi-line report string
298305
*/
299306
public String generateReport() {
300307
ensureComputed();
301308
StringBuilder sb = new StringBuilder();
302309
int n = graph.getVertexCount();
303-
int e = graph.getEdgeCount();
310+
int edgeCount = graph.getEdgeCount();
311+
List<String> vertices = new ArrayList<>(graph.getVertices());
312+
313+
// Single pass over the upper triangle of the distance matrix to
314+
// compute all aggregate metrics simultaneously.
315+
long distSum = 0;
316+
long reachableCount = 0;
317+
double reciprocalSum = 0.0;
318+
long totalPairs = (long) n * (n - 1) / 2;
319+
Map<Integer, Integer> hist = new TreeMap<>();
320+
List<Integer> finiteDistances = new ArrayList<>();
321+
322+
for (int i = 0; i < vertices.size(); i++) {
323+
Map<String, Integer> row = distanceMatrix.get(vertices.get(i));
324+
for (int j = i + 1; j < vertices.size(); j++) {
325+
Integer d = row.get(vertices.get(j));
326+
if (d != null && d > 0) {
327+
distSum += d;
328+
reachableCount++;
329+
reciprocalSum += 1.0 / d;
330+
hist.merge(d, 1, Integer::sum);
331+
finiteDistances.add(d);
332+
}
333+
}
334+
}
335+
336+
double avgPath = reachableCount == 0 ? 0.0 : (double) distSum / reachableCount;
337+
double harmonicMean = reciprocalSum == 0 ? Double.POSITIVE_INFINITY
338+
: (double) totalPairs / reciprocalSum;
339+
double separation = n <= 1 ? 0.0 : 1.0 - (double) reachableCount / totalPairs;
340+
341+
// Sort once for all percentile queries
342+
Collections.sort(finiteDistances);
304343

305344
sb.append("=== Distance Distribution Report ===\n\n");
306-
sb.append(String.format("Vertices: %d | Edges: %d\n", n, e));
307-
sb.append(String.format("Average path length: %.4f\n", getAveragePathLength()));
308-
sb.append(String.format("Wiener index: %d\n", getWienerIndex()));
309-
sb.append(String.format("Harmonic mean distance: %.4f\n", getHarmonicMeanDistance()));
310-
sb.append(String.format("Median distance: %d\n", getMedianDistance()));
311-
sb.append(String.format("90th percentile: %d\n", getDistancePercentile(90)));
312-
sb.append(String.format("95th percentile: %d\n", getDistancePercentile(95)));
313-
sb.append(String.format("Separation ratio: %.4f\n", getSeparationRatio()));
314-
sb.append(String.format("Distinct distances: %d\n\n", getDistinctDistanceCount()));
345+
sb.append(String.format("Vertices: %d | Edges: %d\n", n, edgeCount));
346+
sb.append(String.format("Average path length: %.4f\n", avgPath));
347+
sb.append(String.format("Wiener index: %d\n", distSum));
348+
sb.append(String.format("Harmonic mean distance: %.4f\n", harmonicMean));
349+
sb.append(String.format("Median distance: %d\n", percentileFromSorted(finiteDistances, 50)));
350+
sb.append(String.format("90th percentile: %d\n", percentileFromSorted(finiteDistances, 90)));
351+
sb.append(String.format("95th percentile: %d\n", percentileFromSorted(finiteDistances, 95)));
352+
sb.append(String.format("Separation ratio: %.4f\n", separation));
353+
sb.append(String.format("Distinct distances: %d\n\n", hist.size()));
315354

316-
Map<Integer, Integer> hist = getDistanceHistogram();
317355
if (!hist.isEmpty()) {
318356
sb.append("Distance Histogram:\n");
319-
int maxCount = hist.values().stream().max(Integer::compareTo).orElse(1);
357+
int maxCount = 0;
358+
for (int c : hist.values()) {
359+
if (c > maxCount) maxCount = c;
360+
}
320361
for (Map.Entry<Integer, Integer> entry : hist.entrySet()) {
321362
int barLen = (int) Math.ceil(40.0 * entry.getValue() / maxCount);
322-
String bar = String.join("", Collections.nCopies(barLen, "█"));
323-
sb.append(String.format(" d=%d: %s %d\n", entry.getKey(), bar, entry.getValue()));
363+
StringBuilder bar = new StringBuilder(barLen);
364+
for (int b = 0; b < barLen; b++) bar.append('\u2588');
365+
sb.append(String.format(" d=%d: %s %d\n", entry.getKey(), bar.toString(), entry.getValue()));
324366
}
325367
}
326368

@@ -335,6 +377,20 @@ public String generateReport() {
335377
return sb.toString();
336378
}
337379

380+
/**
381+
* Extracts a percentile from a pre-sorted list of distances.
382+
*
383+
* @param sorted sorted list of finite pairwise distances
384+
* @param percentile the percentile (0–100)
385+
* @return the distance at the given percentile, or -1 if list is empty
386+
*/
387+
private static int percentileFromSorted(List<Integer> sorted, double percentile) {
388+
if (sorted.isEmpty()) return -1;
389+
int index = (int) Math.ceil(percentile / 100.0 * sorted.size()) - 1;
390+
if (index < 0) index = 0;
391+
return sorted.get(index);
392+
}
393+
338394
/**
339395
* Exports the distance matrix as a CSV string.
340396
*

0 commit comments

Comments
 (0)