sclass StringClustersWithIDs { new L clusters; !include #1025301 // change()/onChange() sclass Cluster { GlobalID globalID = aGlobalIDObject(); Set synonyms = ciSet(); *() {} *(S s) { synonyms.add(s); } toString { ret "Cluster " + globalID + ": " + synonyms; } bool add(S s) { ret synonyms.add(s); } } L searchForCluster(S s) { ret filter(clusters, c -> contains(c.synonyms, s)); } Cluster clusterWith(S s) { ret first(searchForCluster(s)); } void add(Cluster cluster) { clusters.add(cluster); change(); } void remove(Cluster cluster) { clusters.remove(cluster); change(); } // also adds to list Cluster newCluster(S s) { Cluster c = new(s); add(c); ret c; } // also adds to list Cluster newCluster(Cl synonyms) { new Cluster c; addAll(c.synonyms, synonyms); add(c); ret c; } // also merges clusters void addPair(S a, S b) { Cluster c1 = clusterWith(a), c2 = clusterWith(b); if (c1 != null) if (c2 != null) mergeClusters(c1, c2); else { c1.add(b); change(); } else if (c2 != null) { c2.add(b); change(); } else newCluster(ll(a, b)); } void mergeClusters(Cluster a, Cluster b) { a.synonyms.addAll(b.synonyms); remove(b); } // either return s or all synonyms from the first cluster containing s Set extend(S s) { Cluster c = clusterWith(s); ret c == null ? litciset(s) : c.synonyms; } }