@@ -106,12 +106,13 @@ public CompressionResult byNeighborhoodSimilarity(double threshold) {
106106 neighborSets .put (v , new HashSet <>(graph .getNeighbors (v )));
107107 }
108108
109+ // Sort vertices by degree to improve pruning effectiveness.
110+ // Vertices with similar degrees are more likely to have high Jaccard
111+ // similarity, so sorting brings candidate pairs closer together.
109112 List <String > vertices = new ArrayList <>(graph .getVertices ());
113+ vertices .sort ((a , b ) -> Integer .compare (neighborSets .get (a ).size (), neighborSets .get (b ).size ()));
114+
110115 boolean [] merged = new boolean [vertices .size ()];
111- Map <String , Integer > indexMap = new HashMap <>();
112- for (int i = 0 ; i < vertices .size (); i ++) {
113- indexMap .put (vertices .get (i ), i );
114- }
115116
116117 List <List <String >> groups = new ArrayList <>();
117118 for (int i = 0 ; i < vertices .size (); i ++) {
@@ -120,11 +121,25 @@ public CompressionResult byNeighborhoodSimilarity(double threshold) {
120121 group .add (vertices .get (i ));
121122 merged [i ] = true ;
122123 Set <String > refNeighbors = neighborSets .get (vertices .get (i ));
124+ int refSize = refNeighbors .size ();
123125
124126 for (int j = i + 1 ; j < vertices .size (); j ++) {
125127 if (merged [j ]) continue ;
126128 Set <String > otherNeighbors = neighborSets .get (vertices .get (j ));
127- double jaccard = jaccardSimilarity (refNeighbors , otherNeighbors );
129+ int otherSize = otherNeighbors .size ();
130+
131+ // Degree-based upper bound pruning: the maximum possible
132+ // Jaccard similarity between two sets is min(|A|,|B|)/max(|A|,|B|).
133+ // Since vertices are sorted by degree, refSize <= otherSize.
134+ // If this upper bound < threshold, no later vertex can match either
135+ // (their degrees only increase), so break early.
136+ if (otherSize > 0 && (double ) refSize / otherSize < threshold ) {
137+ break ;
138+ }
139+
140+ // Compute Jaccard without allocating new sets: count intersection
141+ // by iterating the smaller set and checking the larger.
142+ double jaccard = jaccardFast (refNeighbors , refSize , otherNeighbors , otherSize );
128143 if (jaccard >= threshold ) {
129144 group .add (vertices .get (j ));
130145 merged [j ] = true ;
@@ -136,6 +151,31 @@ public CompressionResult byNeighborhoodSimilarity(double threshold) {
136151 return buildQuotientGraph (groups , "neighborhood_similarity(threshold=" + threshold + ")" );
137152 }
138153
154+ /**
155+ * Computes Jaccard similarity without allocating intermediate HashSets.
156+ * Iterates the smaller set, counting members present in the larger set.
157+ * Union size is derived as |A| + |B| - |intersection|.
158+ *
159+ * @return Jaccard similarity in [0.0, 1.0]
160+ */
161+ private static double jaccardFast (Set <String > a , int aSize , Set <String > b , int bSize ) {
162+ if (aSize == 0 && bSize == 0 ) return 1.0 ;
163+ if (aSize == 0 || bSize == 0 ) return 0.0 ;
164+
165+ // Iterate the smaller set for fewer hash lookups
166+ Set <String > smaller = aSize <= bSize ? a : b ;
167+ Set <String > larger = aSize <= bSize ? b : a ;
168+
169+ int intersection = 0 ;
170+ for (String v : smaller ) {
171+ if (larger .contains (v )) {
172+ intersection ++;
173+ }
174+ }
175+ int union = aSize + bSize - intersection ;
176+ return union == 0 ? 1.0 : (double ) intersection / union ;
177+ }
178+
139179 /**
140180 * Compresses by degree: nodes are grouped into bins by their degree.
141181 *
0 commit comments