-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathUnionFind.java
More file actions
229 lines (200 loc) · 8.39 KB
/
UnionFind.java
File metadata and controls
229 lines (200 loc) · 8.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package com.dbms.queryplan;
import static com.dbms.utils.Helpers.strExpToExp;
import com.dbms.utils.Attribute;
import com.dbms.utils.Catalog;
import com.dbms.utils.Range;
import com.google.common.base.Joiner;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.jsqlparser.expression.Expression;
/** This class reads a selection condition and uses a union-find data structure to keep track of the
* possible bounds of an attribute. */
public class UnionFind {
/** Maps an attribute to its stats, as well as other attributes that share the same stats */
private Map<Attribute, UnionFindElement> elements = new HashMap<>();
/** @return list of attribute sets in each union find element */
public List<Set<Attribute>> getAllAttributeSets() {
List<Set<Attribute>> out = new LinkedList<>();
Set<Set<Attribute>> seenSets = new HashSet<>();
for (UnionFindElement ufe : elements.values()) {
if (!seenSets.contains(ufe.attributes)) {
out.add(ufe.attributes);
seenSets.add(new HashSet<>(ufe.attributes));
}
}
return out;
}
/** @param a attribute with (aliased) table name
* @return extent of values for this attribute within base table range and select */
public int getAttributeExtent(Attribute a) {
Range r = Catalog.STATS.getAttributeRange(a.unalias());
if (!elements.containsKey(a)) return r.extent();
return elements.get(a).extent(r);
}
/** Get the {@code UnionFindElement} associated with an attribute. If it doesn't exist, it puts
* a new record into the {@code UnionFind}.
*
* @param a given attribute
* @return {@code UnionFindElement} containing {@code a}. If one doesn't exist, it creates a new
* one using its stats. */
private UnionFindElement find(Attribute a) {
UnionFindElement result = elements.get(a);
if (result == null) {
result = new UnionFindElement(a);
elements.put(a, result);
return result;
}
return result;
}
/** Creates a select expression containing all conditions with {@code attributes}
*
* @param attributes all attributes of the given table
* @return an expression with all conditions and-ed together */
public Expression expressionOfAttributes(List<Attribute> attributes) {
List<String> stringExpressions = new LinkedList<>();
for (Attribute a : attributes) {
String attributeString = selectExpression(a);
if (attributeString == null) continue;
stringExpressions.add(attributeString);
}
return stringExpressions.size() == 0 ? null : strExpToExp(String.join(" AND ", stringExpressions));
}
/** Creates a select expression with {@code a}
*
* @param a given attribute
* @param seen seen attributes (so no duplicate conditions)
* @return a select condition containing everything with {@code a} */
private String selectExpression(Attribute a) {
if (elements.get(a) == null) return null;
UnionFindElement ufe = elements.get(a);
if (ufe.equality == null && ufe.min == null && ufe.max == null) return null;
if (ufe.equality != null) return String.join(" = ", a.toString(), ufe.equality.toString());
List<String> conditions = new LinkedList<>();
if (ufe.max != null) conditions.add(String.join(" <= ", a.toString(), ufe.max.toString()));
if (ufe.min != null) conditions.add(String.join(" >= ", a.toString(), ufe.min.toString()));
return String.join(" AND ", conditions);
}
/** Updates lower bound of union find element of a given attribute. It sets the minimum to the
* larger of the given and existing min value.
*
* @param a attribute
* @param min potential new minimum value */
public void updateLower(Attribute a, int min) {
UnionFindElement ufe = find(a);
ufe.min = max(ufe.min, min); // largest lower bound
if (ufe.min == ufe.max) ufe.equality = ufe.min;
}
/** Updates upper bound of union find element of a given attribute. It sets the maximum to the
* smaller of the given and existing max value.
*
* @param a attribute
* @param max potential new maximum value */
public void updateUpper(Attribute a, int max) {
UnionFindElement ufe = find(a);
ufe.max = min(ufe.max, max); // smallest upper bound
if (ufe.max == ufe.min) ufe.equality = ufe.min;
}
/** Updates equality relation of an attribute and a number
*
* @param a given attribute
* @param eq value of new equality condition */
public void updateEquality(Attribute a, int eq) {
UnionFindElement ufe = find(a);
ufe.equality = eq;
ufe.max = eq;
ufe.min = eq;
}
/** Merges the properties of 2 {@code Attribute} objects on an equality relation. It will update
* the map containing the {@code UnionFind} data.
*
* @param a
* @param b */
public void union(Attribute a, Attribute b) {
UnionFindElement ufa = find(a);
UnionFindElement ufb = find(b);
ufa.attributes.addAll(ufb.attributes);
Integer newEq = ufa.equality != null ? ufa.equality : ufb.equality;
Integer newMin = newEq == null ? max(ufa.min, ufb.min) : newEq;
Integer newMax = newEq == null ? min(ufa.max, ufb.max) : newEq;
UnionFindElement ufe = new UnionFindElement(ufa.attributes, newMax, newMin, newEq);
ufe.attributes.forEach(attr -> elements.put(attr, ufe));
}
@Override
public String toString() {
List<String> result = new LinkedList<>();
Set<Attribute> seen = new HashSet<>();
elements.forEach((attr, ufe) -> {
if (seen.contains(attr)) return;
seen.addAll(ufe.attributes);
result.add(ufe.toString());
});
return Joiner.on("\n").join(result);
}
/** Maximum value with {@code null} support
*
* @param a
* @param b
* @return {@code max(a,b)}; if either is {@code null}, return the non-{@code null} one;
* otherwise, return {@code null} */
private Integer max(Integer a, Integer b) {
if (a == null) return b;
if (b == null) return a;
return Math.max(a, b);
}
/** Minimum value with {@code null} support
*
* @param a
* @param b
* @return {@code max(a,b)}; if either is {@code null}, return the non-{@code null} one;
* otherwise, return {@code null} */
private Integer min(Integer a, Integer b) {
if (a == null) return b;
if (b == null) return a;
return Math.min(a, b);
}
}
/** Keeps track of multiple attributes that all fall within a range with their data. */
class UnionFindElement {
/** The set of attributes represented by this element. */
Set<Attribute> attributes = new HashSet<>();
/** Maximum possible element (inclusive) of all attributes in {@code attributes} */
Integer max = null;
/** Minimum possible element (inclusive) of all attributes in {@code attributes} */
Integer min = null;
/** All attributes are equal to {@code equality} if it is not null. {@code min} and {@code max}
* will be updated accordingly */
Integer equality = null;
/** Creates a {@code UnionFindElement} with a single attribute
*
* @param attribute set of attributes */
UnionFindElement(Attribute a) {
attributes.add(a);
}
/** @param sa set of attributes
* @param max upper bound
* @param min lower bound
* @param equality equality */
UnionFindElement(Set<Attribute> sa, Integer max, Integer min, Integer equality) {
attributes = sa;
this.max = max;
this.min = min;
this.equality = max == min ? min : equality;
}
/** @param r range of values to use if null bounds
* @return number of values represented by this element's bounds within the range */
public int extent(Range r) {
int top = max != null ? max : r.max;
int bottom = min != null ? min : r.min;
return top - bottom + 1;
}
@Override
public String toString() {
return Arrays.asList(attributes.toString(), "equals " + equality, "min " + min, "max " + max)
.toString();
}
}