package com.ibm.text.utility; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; public class XEquivalenceClass { // quick test static public void main(String[] args) { XEquivalenceClass foo1 = new XEquivalenceClass("NONE"); String[][] tests = {{"b","a1"}, {"b", "c"}, {"a1", "c"}, {"d", "e"}, {"e", "f"}, {"c", "d"}}; for (int i = 0; i < tests.length; ++i) { System.out.println("Adding: " + tests[i][0] + ", " + tests[i][1]); foo1.add(tests[i][0], tests[i][1], new Integer(i)); for (Iterator it = foo1.getExplicitItems().iterator(); it.hasNext();) { Object item = it.next(); System.out.println("\t" + item + ";\t" + foo1.getSample(item) + ";\t" + foo1.getEquivalences(item)); System.out.println("\t\t" + foo1.getReasons(item, foo1.getSample(item))); } } } private Map toPartitionSet = new HashMap(); private Map obj_obj_reasons = new HashMap(); private Object defaultReason; /** * Create class with comparator, and default reason. * */ public XEquivalenceClass(Object defaultReason) { this.defaultReason = defaultReason; } /** * Add two equivalent items, with NO_REASON for the reason. */ public XEquivalenceClass add(Object a, Object b) { return add(a,b,null); } /** * Add two equivalent items, plus a reason. The reason is only used for getReasons */ public XEquivalenceClass add(Object a, Object b, Object reason) { if (a.equals(b)) return this; if (reason == null) reason = defaultReason; addReason(a,b,reason); addReason(b,a,reason); Set aPartitionSet = (Set) toPartitionSet.get(a); Set bPartitionSet = (Set) toPartitionSet.get(b); if (aPartitionSet == null) { if (bPartitionSet == null) { // both null, set up bSet bPartitionSet = new HashSet(); bPartitionSet.add(b); toPartitionSet.put(b, bPartitionSet); } bPartitionSet.add(a); toPartitionSet.put(a, bPartitionSet); } else if (bPartitionSet == null) { // aSet is not null, bSet null aPartitionSet.add(b); toPartitionSet.put(b, aPartitionSet); } else if (aPartitionSet != bPartitionSet) { // both non-null, not equal, merge. Equality check ok here aPartitionSet.addAll(bPartitionSet); // remap every x that had x => bPartitionSet for (Iterator it = bPartitionSet.iterator(); it.hasNext();) { toPartitionSet.put(it.next(), aPartitionSet); } } return this; } /** * Add all the information from the other class * */ public XEquivalenceClass addAll(XEquivalenceClass other) { // For now, does the simple, not optimized version for (Iterator it = other.obj_obj_reasons.keySet().iterator(); it.hasNext();) { Object a = it.next(); Map obj_reasons = (Map) other.obj_obj_reasons.get(a); for (Iterator it2 = obj_reasons.keySet().iterator(); it2.hasNext();) { Object b = it2.next(); Set reasons = (Set) obj_reasons.get(b); for (Iterator it3 = reasons.iterator(); it3.hasNext();) { Object reason = it3.next(); add(a, b, reason); } } } return this; } /** * */ private void addReason(Object a, Object b, Object reason) { Map obj_reasons = (Map) obj_obj_reasons.get(a); if (obj_reasons == null) obj_obj_reasons.put(a, obj_reasons = new HashMap()); Set reasons = (Set) obj_reasons.get(b); if (reasons == null) obj_reasons.put(b, reasons = new HashSet()); reasons.add(reason); } /** * Returns a set of all the explicit items in the equivalence set. (Any non-explicit items only * have themselves as equivalences.) * */ public Set getExplicitItems() { return Collections.unmodifiableSet(toPartitionSet.keySet()); } /** * Returns an unmodifiable set of all the equivalent objects * */ public Set getEquivalences(Object a) { Set aPartitionSet = (Set) toPartitionSet.get(a); if (aPartitionSet == null) { // manufacture an equivalence aPartitionSet = new HashSet(); aPartitionSet.add(a); } return Collections.unmodifiableSet(aPartitionSet); } public Set getEquivalenceSets() { Set result = new HashSet(); for (Iterator it = toPartitionSet.keySet().iterator(); it.hasNext();) { Object item = it.next(); Set partition = (Set) toPartitionSet.get(item); result.add(Collections.unmodifiableSet(partition)); } return result; } /** * returns true iff a is equivalent to b (or a.equals b) * */ public boolean isEquivalent(Object a, Object b) { if (a.equals(b)) return true; Set aPartitionSet = (Set) toPartitionSet.get(a); if (aPartitionSet == null) return false; return aPartitionSet.contains(b); } /** * Gets a sample object in the equivalence set for a. * */ public Object getSample(Object a) { Set aPartitionSet = (Set) toPartitionSet.get(a); if (aPartitionSet == null) return a; // singleton return aPartitionSet.iterator().next(); } public interface Filter { boolean matches(Object o); } public Object getSample(Object a, Filter f) { Set aPartitionSet = (Set) toPartitionSet.get(a); if (aPartitionSet == null) return a; // singleton for (Iterator it = aPartitionSet.iterator(); it.hasNext();) { Object obj = it.next(); if (f.matches(obj)) return obj; } return a; } /** * gets the set of all the samples, one from each equivalence class. * */ public Set getSamples() { Set seenAlready = new HashSet(); Set result = new HashSet(); for (Iterator it = toPartitionSet.keySet().iterator(); it.hasNext();) { Object item = it.next(); if (seenAlready.contains(item)) continue; Set partition = (Set) toPartitionSet.get(item); result.add(partition.iterator().next()); seenAlready.addAll(partition); } return result; } /** * Returns a list of lists. Each sublist is in the form [reasons, obj, reasons, obj,..., reasons] * where each reasons is a set of reasons to go from one obj to the next.
* Returns null if there is no connection. */ public List getReasons(Object a, Object b) { // use dumb algorithm for getting shortest path // don't bother with optimization Set aPartitionSet = (Set) toPartitionSet.get(a); Set bPartitionSet = (Set) toPartitionSet.get(b); // see if they connect if (aPartitionSet == null || bPartitionSet == null || aPartitionSet != bPartitionSet || a.equals(b)) return null; ArrayList list = new ArrayList(); list.add(a); ArrayList lists = new ArrayList(); lists.add(list); // this will contain the results List foundLists = new ArrayList(); Set sawLastTime = new HashSet(); sawLastTime.add(a); // each time, we extend the lists by one (adding multiple other lists) while (foundLists.size() == 0) { ArrayList extendedList = new ArrayList(); Set sawThisTime = new HashSet(); for (Iterator it = lists.iterator(); it.hasNext();) { ArrayList lista = (ArrayList) it.next(); Object last = lista.get(lista.size()-1); Map obj_reasons = (Map) obj_obj_reasons.get(last); for (Iterator it2 = obj_reasons.keySet().iterator(); it2.hasNext();) { Object item = it2.next(); if (sawLastTime.contains(item)) { continue; // skip since we have shorter } sawThisTime.add(item); Set reasons = (Set) obj_reasons.get(item); ArrayList lista2 = (ArrayList)lista.clone(); lista2.add(reasons); lista2.add(item); extendedList.add(lista2); if (item.equals(b)) { // remove first and last ArrayList found = (ArrayList)lista2.clone(); found.remove(0); found.remove(found.size()-1); foundLists.add(found); } } } lists = extendedList; sawLastTime.addAll(sawThisTime); } return foundLists; } }