• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *******************************************************************************
3  * Copyright (C) 1996-2013, International Business Machines Corporation and    *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 package org.unicode.cldr.util;
8 
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17 
18 import com.ibm.icu.text.Transform;
19 
20 public class XEquivalenceClass<T, R> implements Iterable<T> {
21     private static final String ARROW = "\u2192";
22 
getSetMaker()23     public SetMaker<T> getSetMaker() {
24         return setMaker;
25     }
26 
27     // quick test
main(String[] args)28     static public void main(String[] args) {
29         XEquivalenceClass<String, Integer> foo1 = new XEquivalenceClass<>(1);
30         String[][] tests = { { "b", "a1" }, { "b", "c" }, { "a1", "c" }, { "d", "e" }, { "e", "f" }, { "c", "d" } };
31         for (int i = 0; i < tests.length; ++i) {
32             System.out.println("Adding: " + tests[i][0] + ", " + tests[i][1]);
33             foo1.add(tests[i][0], tests[i][1], new Integer(i));
34             for (String item : foo1.getExplicitItems()) {
35                 System.out.println("\t" + item + ";\t" + foo1.getSample(item) + ";\t" + foo1.getEquivalences(item));
36                 List<Linkage<String, Integer>> reasons = foo1.getReasons(item, foo1.getSample(item));
37                 if (reasons != null) {
38                     System.out.println("\t\t" + XEquivalenceClass.toString(reasons, null));
39                 }
40             }
41         }
42     }
43 
44     private Map<T, Set<T>> toPartitionSet = new HashMap();
45     private Map<T, Map<T, Set<R>>> obj_obj_reasons = new HashMap();
46     private R defaultReason;
47     private SetMaker setMaker;
48 
49     public interface SetMaker<T> {
make()50         Set<T> make();
51     }
52 
53     /**
54      * empty, as if just created
55      */
clear(R defaultReasonArg)56     public XEquivalenceClass clear(R defaultReasonArg) {
57         toPartitionSet.clear();
58         obj_obj_reasons.clear();
59         this.defaultReason = defaultReasonArg;
60         return this;
61     }
62 
63     /**
64      * Create class
65      *
66      */
XEquivalenceClass()67     public XEquivalenceClass() {
68     }
69 
70     /**
71      * Create class with comparator, and default reason.
72      *
73      */
XEquivalenceClass(R defaultReason)74     public XEquivalenceClass(R defaultReason) {
75         this.defaultReason = defaultReason;
76     }
77 
78     /**
79      * Create class with comparator, and default reason.
80      *
81      */
XEquivalenceClass(R defaultReason, SetMaker<T> setMaker)82     public XEquivalenceClass(R defaultReason, SetMaker<T> setMaker) {
83         this.defaultReason = defaultReason;
84         this.setMaker = setMaker;
85     }
86 
87     /**
88      * Add two equivalent items, with NO_REASON for the reason.
89      */
add(T a, T b)90     public XEquivalenceClass add(T a, T b) {
91         return add(a, b, null);
92     }
93 
94     /**
95      * Add two equivalent items, with NO_REASON for the reason.
96      */
add(T a, T b, R reason)97     public XEquivalenceClass add(T a, T b, R reason) {
98         return add(a, b, reason, reason);
99     }
100 
101     /**
102      * Add two equivalent items, plus a reason. The reason is only used for getReasons
103      */
add(T a, T b, R reasonAB, R reasonBA)104     public XEquivalenceClass add(T a, T b, R reasonAB, R reasonBA) {
105         if (a.equals(b)) return this;
106         if (reasonAB == null) reasonAB = defaultReason;
107         if (reasonBA == null) reasonBA = defaultReason;
108         addReason(a, b, reasonAB);
109         addReason(b, a, reasonBA);
110         Set<T> aPartitionSet = toPartitionSet.get(a);
111         Set<T> bPartitionSet = toPartitionSet.get(b);
112         if (aPartitionSet == null) {
113             if (bPartitionSet == null) { // both null, set up bSet
114                 bPartitionSet = setMaker != null ? setMaker.make() : new HashSet();
115                 bPartitionSet.add(b);
116                 toPartitionSet.put(b, bPartitionSet);
117             }
118             bPartitionSet.add(a);
119             toPartitionSet.put(a, bPartitionSet);
120         } else if (bPartitionSet == null) { // aSet is not null, bSet null
121             aPartitionSet.add(b);
122             toPartitionSet.put(b, aPartitionSet);
123         } else if (aPartitionSet != bPartitionSet) { // both non-null, not equal, merge.  Equality check ok here
124             aPartitionSet.addAll(bPartitionSet);
125             // remap every x that had x => bPartitionSet
126             for (T item : bPartitionSet) {
127                 toPartitionSet.put(item, aPartitionSet);
128             }
129         }
130         return this;
131     }
132 
133     /**
134      * Add all the information from the other class
135      *
136      */
addAll(XEquivalenceClass<T, R> other)137     public XEquivalenceClass<T, R> addAll(XEquivalenceClass<T, R> other) {
138         // For now, does the simple, not optimized version
139         for (T a : other.obj_obj_reasons.keySet()) {
140             Map<T, Set<R>> obj_reasons = other.obj_obj_reasons.get(a);
141             for (T b : obj_reasons.keySet()) {
142                 Set<R> reasons = obj_reasons.get(b);
143                 for (R reason : reasons) {
144                     add(a, b, reason);
145                 }
146             }
147         }
148         return this;
149     }
150 
151     /**
152      *
153      */
addReason(T a, T b, R reason)154     private void addReason(T a, T b, R reason) {
155         Map<T, Set<R>> obj_reasons = obj_obj_reasons.get(a);
156         if (obj_reasons == null) obj_obj_reasons.put(a, obj_reasons = new HashMap());
157         Set<R> reasons = obj_reasons.get(b);
158         if (reasons == null) obj_reasons.put(b, reasons = new HashSet());
159         reasons.add(reason);
160     }
161 
162     /**
163      * Returns a set of all the explicit items in the equivalence set. (Any non-explicit items only
164      * have themselves as equivalences.)
165      *
166      */
getExplicitItems()167     public Set<T> getExplicitItems() {
168         return Collections.unmodifiableSet(toPartitionSet.keySet());
169     }
170 
171     /**
172      * Returns an unmodifiable set of all the equivalent objects
173      *
174      */
getEquivalences(T a)175     public Set<T> getEquivalences(T a) {
176         Set<T> aPartitionSet = toPartitionSet.get(a);
177         if (aPartitionSet == null) { // manufacture an equivalence
178             aPartitionSet = new HashSet<>();
179             aPartitionSet.add(a);
180         }
181         return Collections.unmodifiableSet(aPartitionSet);
182     }
183 
hasEquivalences(T a)184     public boolean hasEquivalences(T a) {
185         return toPartitionSet.get(a) != null;
186     }
187 
getEquivalenceSets()188     public Set<Set<T>> getEquivalenceSets() {
189         Set<Set<T>> result = new HashSet<>();
190         for (T item : toPartitionSet.keySet()) {
191             Set<T> partition = toPartitionSet.get(item);
192             result.add(Collections.unmodifiableSet(partition));
193         }
194         return result;
195     }
196 
197     /**
198      * returns true iff a is equivalent to b (or a.equals b)
199      *
200      */
isEquivalent(T a, T b)201     public boolean isEquivalent(T a, T b) {
202         if (a.equals(b)) return true;
203         Set<T> aPartitionSet = toPartitionSet.get(a);
204         if (aPartitionSet == null) return false;
205         return aPartitionSet.contains(b);
206     }
207 
208     /**
209      * Gets a sample object in the equivalence set for a.
210      *
211      */
getSample(T a)212     public T getSample(T a) {
213         Set<T> aPartitionSet = toPartitionSet.get(a);
214         if (aPartitionSet == null) return a; // singleton
215         return aPartitionSet.iterator().next();
216     }
217 
218     public interface Filter<T> {
matches(T o)219         boolean matches(T o);
220     }
221 
getSample(T a, Filter<T> f)222     public T getSample(T a, Filter<T> f) {
223         Set<T> aPartitionSet = toPartitionSet.get(a);
224         if (aPartitionSet == null) return a; // singleton
225         for (T obj : aPartitionSet) {
226             if (f.matches(obj)) return obj;
227         }
228         return a;
229     }
230 
231     /**
232      * gets the set of all the samples, one from each equivalence class.
233      *
234      */
getSamples()235     public Set<T> getSamples() {
236         Set<T> seenAlready = new HashSet();
237         Set<T> result = new HashSet();
238         for (T item : toPartitionSet.keySet()) {
239             if (seenAlready.contains(item)) continue;
240             Set<T> partition = toPartitionSet.get(item);
241             result.add(partition.iterator().next());
242             seenAlready.addAll(partition);
243         }
244         return result;
245     }
246 
247     @Override
iterator()248     public Iterator<T> iterator() {
249         return getSamples().iterator();
250     }
251 
252     public static class Linkage<T, R> {
253         public Set<R> reasons;
254         public T result;
255 
Linkage(Set<R> reasons, T result)256         public Linkage(Set<R> reasons, T result) {
257             this.reasons = reasons;
258             this.result = result;
259         }
260 
261         @Override
toString()262         public String toString() {
263             return reasons + (result == null ? "" : ARROW + result);
264         }
265     }
266 
toString(List<Linkage<T, R>> others, Transform<Linkage<T, R>, String> itemTransform)267     public static <T, R> String toString(List<Linkage<T, R>> others, Transform<Linkage<T, R>, String> itemTransform) {
268         StringBuffer result = new StringBuffer();
269         for (Linkage<T, R> item : others) {
270             result.append(itemTransform == null ? item.toString() : itemTransform.transform(item));
271         }
272         return result.toString();
273     }
274 
275     /**
276      * Returns a list of linkages, where each set of reasons to go from one obj to the next. The list does not include a and b themselves.
277      * The last linkage has a null result.<br>
278      * Returns null if there is no connection.
279      */
getReasons(T a, T b)280     public List<Linkage<T, R>> getReasons(T a, T b) {
281         // use dumb algorithm for getting shortest path
282         // don't bother with optimization
283         Set<T> aPartitionSet = toPartitionSet.get(a);
284         Set<T> bPartitionSet = toPartitionSet.get(b);
285 
286         // see if they connect
287         if (aPartitionSet == null || bPartitionSet == null || aPartitionSet != bPartitionSet || a.equals(b)) return null;
288 
289         ArrayList<Linkage<T, R>> list = new ArrayList<>();
290         list.add(new Linkage(null, a));
291         ArrayList<ArrayList<Linkage<T, R>>> lists = new ArrayList<>();
292         lists.add(list);
293 
294         // this will contain the results
295         Set<T> sawLastTime = new HashSet<>();
296         sawLastTime.add(a);
297 
298         // each time, we extend each lists by one (adding multiple other lists)
299         while (true) { // foundLists.size() == 0
300             ArrayList extendedList = new ArrayList();
301             Set<T> sawThisTime = new HashSet();
302             for (ArrayList<Linkage<T, R>> lista : lists) {
303                 Linkage<T, R> last = lista.get(lista.size() - 1);
304                 Map<T, Set<R>> obj_reasons = obj_obj_reasons.get(last.result);
305                 for (T result : obj_reasons.keySet()) {
306                     if (sawLastTime.contains(result)) {
307                         continue; // skip since we have shorter
308                     }
309                     sawThisTime.add(result);
310                     Set<R> reasons = obj_reasons.get(result);
311                     ArrayList<Linkage<T, R>> lista2 = (ArrayList<Linkage<T, R>>) lista.clone();
312                     lista2.add(new Linkage(reasons, result));
313                     extendedList.add(lista2);
314                     if (result.equals(b)) {
315                         // remove first and last
316                         ArrayList<Linkage<T, R>> found = (ArrayList<Linkage<T, R>>) lista2.clone();
317                         found.remove(0);
318                         found.get(found.size() - 1).result = null;
319                         return found;
320                     }
321                 }
322             }
323             lists = extendedList;
324             sawLastTime.addAll(sawThisTime);
325         }
326         // return foundLists;
327     }
328 
329     /**
330      * For debugging.
331      */
332     @Override
toString()333     public String toString() {
334         return getEquivalenceSets().toString();
335     }
336 }