org.apache.calcite.util.PartiallyOrderedSet Maven / Gradle / Ivy
Show all versions of calcite-core Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.util;
import com.google.common.collect.ImmutableList;
import java.util.AbstractSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
/**
* Partially-ordered set.
*
* When you create a partially-ordered set ('poset' for short) you must
* provide an {@link Ordering} that determines the order relation. The
* ordering must be:
*
*
* - reflexive: e.lte(e) returns true;
* - anti-symmetric: if e.lte(f) returns true,
* then f.lte(e) returns false only if e = f;
* - transitive: if e.lte(f) returns true and
* f.lte(g) returns true, then e.lte(g) must return true.
*
*
* Note that not all pairs of elements are related. If is OK if e.lte(f)
* returns false and f.lte(e) returns false also.
*
* In addition to the usual set methods, there are methods to determine the
* immediate parents and children of an element in the set, and method to find
* all elements which have no parents or no children (i.e. "root" and "leaf"
* elements).
*
* A lattice is a special kind of poset where there is a unique top and
* bottom element. You can use a PartiallyOrderedSet for a lattice also. It may
* be helpful to add the top and bottom elements to the poset on
* construction.
*
* @param Element type
*/
public class PartiallyOrderedSet extends AbstractSet {
/** Ordering that orders bit sets by inclusion.
*
* For example, the children of 14 (1110) are 12 (1100), 10 (1010) and
* 6 (0110).
*/
public static final Ordering BIT_SET_INCLUSION_ORDERING =
ImmutableBitSet::contains;
private final Map> map;
private final Function> parentFunction;
private final Function> childFunction;
private final Ordering ordering;
/**
* Synthetic node to hold all nodes that have no parents. It does not appear
* in the set.
*/
private final Node topNode;
private final Node bottomNode;
/** Whether to check internal consistency all the time.
* False unless you specify "-Dcalcite.debug" on the command line. */
private static final boolean DEBUG = Util.getBooleanProperty("calcite.debug");
/**
* Creates a partially-ordered set.
*
* @param ordering Ordering relation
*/
public PartiallyOrderedSet(Ordering ordering) {
this(ordering, new HashMap<>(), null, null);
}
/**
* Creates a partially-ordered set with a parent-generating function.
*
* @param ordering Ordering relation
* @param parentFunction Function to compute parents of a node; may be null
*/
public PartiallyOrderedSet(Ordering ordering,
Function> childFunction,
Function> parentFunction) {
this(ordering, new HashMap<>(), childFunction, parentFunction);
}
@SuppressWarnings("Guava")
@Deprecated // to be removed before 2.0
public PartiallyOrderedSet(Ordering ordering,
com.google.common.base.Function> childFunction,
com.google.common.base.Function> parentFunction) {
this(ordering, (Function>) childFunction::apply,
parentFunction::apply);
}
/**
* Creates a partially-ordered set, and populates it with a given
* collection.
*
* @param ordering Ordering relation
* @param collection Initial contents of partially-ordered set
*/
public PartiallyOrderedSet(Ordering ordering, Collection collection) {
this(ordering, new HashMap<>(collection.size() * 3 / 2), null, null);
addAll(collection);
}
/**
* Internal constructor.
*
* @param ordering Ordering relation
* @param map Map from values to nodes
* @param parentFunction Function to compute parents of a node; may be null
*/
private PartiallyOrderedSet(Ordering ordering, Map> map,
Function> childFunction,
Function> parentFunction) {
this.ordering = ordering;
this.map = map;
this.childFunction = childFunction;
this.parentFunction = parentFunction;
this.topNode = new TopBottomNode<>(true);
this.bottomNode = new TopBottomNode<>(false);
this.topNode.childList.add(bottomNode);
this.bottomNode.parentList.add(topNode);
}
@SuppressWarnings("NullableProblems")
@Override public Iterator iterator() {
final Iterator iterator = map.keySet().iterator();
return new Iterator() {
E previous;
public boolean hasNext() {
return iterator.hasNext();
}
public E next() {
return previous = iterator.next();
}
public void remove() {
if (!PartiallyOrderedSet.this.remove(previous)) {
// Object was not present.
// Maybe they have never called 'next'?
// Maybe they called 'remove' twice?
// Either way, something is screwy.
throw new IllegalStateException();
}
}
};
}
@Override public int size() {
return map.size();
}
@Override public boolean contains(Object o) {
//noinspection SuspiciousMethodCalls
return map.containsKey(o);
}
@Override public boolean remove(Object o) {
@SuppressWarnings("SuspiciousMethodCalls")
final Node node = map.remove(o);
if (node == null) {
return false;
}
for (int i = 0; i < node.parentList.size(); i++) {
Node parent = node.parentList.get(i);
for (Node child : node.childList) {
if (parent.e == null && child.e == null) {
parent.childList.remove(node);
continue;
}
replace(parent.childList, node, child);
}
}
for (int i = 0; i < node.childList.size(); i++) {
Node child = node.childList.get(i);
for (Node parent : node.parentList) {
if (child.e == null && parent.e == null) {
child.parentList.remove(node);
continue;
}
replace(child.parentList, node, parent);
}
}
return true;
}
/**
* Adds an element to this lattice.
*/
@Override public boolean add(E e) {
assert e != null;
assert !DEBUG || isValid(true);
Node node = map.get(e);
if (node != null) {
// already present
return false;
}
Set> parents = findParents(e);
Set> children = findChildren(e);
node = new Node<>(e);
for (Node parent : parents) {
node.parentList.add(parent);
int n = 0;
for (int i = 0; i < parent.childList.size(); i++) {
Node child = parent.childList.get(i);
if (child.e == null || ordering.lessThan(child.e, e)) {
if (parent.childList.contains(node)) {
parent.childList.remove(i);
--i;
} else {
parent.childList.set(i, node);
}
replace(child.parentList, parent, node);
if (!node.childList.contains(child)) {
node.childList.add(child);
}
++n;
}
}
if (n == 0) {
parent.childList.add(node);
}
}
// Nodes reachable from parents.
final Set> childSet = new HashSet<>(node.childList);
for (Node child : children) {
if (!isDescendantOfAny(child, childSet)) {
node.childList.add(child);
if (!child.parentList.contains(node)) {
child.parentList.add(node);
}
}
}
map.put(node.e, node);
assert !DEBUG || isValid(true);
return true;
}
/**
* Returns whether node's value is a descendant of any of the values in
* nodeSet.
*
* @param node Node
* @param nodeSet Suspected ancestors
* @return Whether node is a descendant of any of the nodes
*/
private boolean isDescendantOfAny(
Node node,
Set> nodeSet) {
final Deque> deque = new ArrayDeque<>();
final Set> seen = new HashSet<>();
deque.add(node);
while (!deque.isEmpty()) {
final Node node1 = deque.pop();
if (nodeSet.contains(node1)) {
return true;
}
for (Node parent : node1.parentList) {
if (seen.add(parent)) {
deque.add(parent);
}
}
}
return false;
}
private Set> findChildren(E e) {
final Deque> descendants = new ArrayDeque<>();
descendants.add(bottomNode);
return findParentsChildren(e, descendants, false);
}
private Set> findParents(E e) {
final Deque> ancestors = new ArrayDeque<>();
ancestors.add(topNode);
return findParentsChildren(e, ancestors, true);
}
private Set> findParentsChildren(
E e,
Deque> ancestors,
boolean up) {
final Set> parents = new HashSet<>();
while (!ancestors.isEmpty()) {
final Node ancestor = ancestors.pop();
assert ancestor.e == null
|| (up
? !ordering.lessThan(ancestor.e, e)
: !ordering.lessThan(e, ancestor.e));
assert ancestor.e != e; // e not in tree yet
// Example: e = "a", ancestor = "abc".
// If there is at least one child c of ancestor such that e <= c
// (e.g. "ab", "ac") examine those children. If there are no
// such children, ancestor becomes a parent
int found = 0;
for (Node child : up ? ancestor.childList : ancestor.parentList) {
if (child.e == null) {
continue; // child is the bottom node
}
if (up
? ordering.lessThan(e, child.e)
: ordering.lessThan(child.e, e)) {
ancestors.add(child);
++found;
} else if (up
? !ordering.lessThan(child.e, e)
: !ordering.lessThan(e, child.e)) {
// e is not less than child (and therefore some descendant
// of child might be less than e). Recurse into its
// children.
ancestors.add(child);
}
}
if (found == 0) {
// None of the descendants of the node are greater than e.
// Therefore node is a parent, provided that e is definitely
// less than node
if (ancestor.e == null
|| (up
? ordering.lessThan(e, ancestor.e)
: ordering.lessThan(ancestor.e, e))) {
parents.add(ancestor);
}
}
}
return parents;
}
private void replace(List list, T remove, T add) {
if (list.contains(add)) {
list.remove(remove);
} else {
final int index = list.indexOf(remove);
if (index >= 0) {
list.set(index, add);
} else {
list.add(add);
}
}
}
/**
* Checks internal consistency of this lattice.
*
* @param fail Whether to throw an assertion error
* @return Whether valid
*/
@SuppressWarnings({"ConstantConditions" })
public boolean isValid(boolean fail) {
// Top has no parents.
// Bottom has no children.
// Each node except top has at least one parent.
// Each node except bottom has at least one child.
// Every node's children list it as a parent.
// Every node's parents list it as a child.
for (Node node : map.values()) {
if ((node == topNode)
!= (node.parentList.isEmpty())) {
assert !fail
: "only top node should have no parents " + node
+ ", parents " + node.parentList;
return false;
}
if ((node == bottomNode)
!= (node.childList.isEmpty())) {
assert !fail
: "only bottom node should have no children " + node
+ ", children " + node.childList;
return false;
}
for (int i = 0; i < node.childList.size(); i++) {
Node child = node.childList.get(i);
if (!child.parentList.contains(node)) {
assert !fail
: "child " + child + " of " + node
+ " does not know its parent";
return false;
}
if (child.e != null && !ordering.lessThan(child.e, node.e)) {
assert !fail
: "child " + child.e + " not less than parent "
+ node.e;
return false;
}
for (int i2 = 0; i2 < node.childList.size(); i2++) {
Node child2 = node.childList.get(i2);
if (child == child2
&& i != i2) {
assert !fail
: "duplicate child " + child + " of parent " + node;
return false;
}
if (child.e != null
&& child2.e != null
&& child != child2
&& ordering.lessThan(child.e, child2.e)) {
assert !fail
: "relation between children " + child.e
+ " and " + child2.e + " of node " + node.e;
return false;
}
}
}
for (Node parent : node.parentList) {
if (!parent.childList.contains(node)) {
assert !fail;
return false;
}
}
}
final Map distanceToRoot = new HashMap<>();
distanceRecurse(distanceToRoot, topNode, 0);
for (Node node : map.values()) {
if (!distanceToRoot.containsKey(node)) {
assert !fail : "node " + node + " is not reachable";
return false;
}
}
// For each pair of elements, ensure that elements are related if and
// only if they are in the ancestors or descendants lists.
final Map, Set> nodeAncestors = new HashMap<>();
final Map, Set> nodeDescendants = new HashMap<>();
for (Node node : map.values()) {
nodeAncestors.put(node, new HashSet<>(getAncestors(node.e)));
nodeDescendants.put(node, new HashSet<>(getDescendants(node.e)));
}
for (Node node1 : map.values()) {
for (Node node2 : map.values()) {
final boolean lt12 = ordering.lessThan(node1.e, node2.e);
final boolean lt21 = ordering.lessThan(node2.e, node1.e);
if (node1 == node2) {
if (!(lt12 && lt21)) {
assert !fail
: "self should be less than self: " + node1;
}
}
if (lt12 && lt21) {
if (!(node1 == node2)) {
assert !fail
: "node " + node1.e + " and node " + node2.e
+ " are less than each other but are not the same"
+ " value";
return false;
}
}
if (lt12 && !lt21) {
if (!nodeAncestors.get(node1).contains(node2.e)) {
assert !fail
: node1.e + " is less than " + node2.e + " but "
+ node2.e + " is not in the ancestor set of "
+ node1.e;
return false;
}
if (!nodeDescendants.get(node2).contains(node1.e)) {
assert !fail
: node1.e + " is less than " + node2.e + " but "
+ node1.e + " is not in the descendant set of "
+ node2.e;
return false;
}
}
if (lt21 && !lt12) {
if (!nodeAncestors.get(node2).contains(node1.e)) {
assert !fail
: node2.e + " is less than " + node1.e + " but "
+ node1.e + " is not in the ancestor set of "
+ node2.e;
return false;
}
if (!nodeDescendants.get(node1).contains(node2.e)) {
assert !fail
: node2.e + " is less than " + node1.e + " but "
+ node2.e + " is not in the descendant set of "
+ node1.e;
return false;
}
}
}
}
return true;
}
private void distanceRecurse(
Map distanceToRoot,
Node node,
int distance) {
final Integer best = distanceToRoot.get(node);
if (best == null || distance < best) {
distanceToRoot.put(node, distance);
}
if (best != null) {
return;
}
for (Node child : node.childList) {
distanceRecurse(
distanceToRoot,
child,
distance + 1);
}
}
public void out(StringBuilder buf) {
buf.append("PartiallyOrderedSet size: ");
buf.append(size());
buf.append(" elements: {\n");
// breadth-first search, to iterate over every element once, printing
// those nearest the top element first
final Set seen = new HashSet<>();
final Deque unseen = new ArrayDeque<>(getNonChildren());
while (!unseen.isEmpty()) {
E e = unseen.pop();
buf.append(" ");
buf.append(e);
buf.append(" parents: ");
final List parents = getParents(e);
buf.append(parents);
buf.append(" children: ");
final List children = getChildren(e);
buf.append(children);
buf.append("\n");
for (E child : children) {
if (seen.add(child)) {
unseen.add(child);
}
}
}
buf.append("}");
}
/**
* Returns the values in this partially-ordered set that are less-than
* a given value and there are no intervening values.
*
* If the value is not in this set, returns null.
*
* @see #getDescendants
*
* @param e Value
* @return List of values in this set that are directly less than the given
* value
*/
public List getChildren(E e) {
return getChildren(e, false);
}
/**
* Returns the values in this partially-ordered set that are less-than
* a given value and there are no intervening values.
*
* If the value is not in this set, returns null if {@code hypothetical}
* is false.
*
* @see #getDescendants
*
* @param e Value
* @param hypothetical Whether to generate a list if value is not in the set
* @return List of values in this set that are directly less than the given
* value
*/
public List getChildren(E e, boolean hypothetical) {
final Node node = map.get(e);
if (node == null) {
if (hypothetical) {
return strip(findChildren(e));
} else {
return null;
}
} else {
return strip(node.childList);
}
}
/**
* Returns the values in this partially-ordered set that are greater-than
* a given value and there are no intervening values.
*
* If the value is not in this set, returns null.
*
* @see #getAncestors
*
* @param e Value
* @return List of values in this set that are directly greater than the
* given value
*/
public List getParents(E e) {
return getParents(e, false);
}
/**
* Returns the values in this partially-ordered set that are greater-than
* a given value and there are no intervening values.
*
* If the value is not in this set, returns {@code null} if
* {@code hypothetical} is false.
*
* @see #getAncestors
*
* @param e Value
* @param hypothetical Whether to generate a list if value is not in the set
* @return List of values in this set that are directly greater than the
* given value
*/
public List getParents(E e, boolean hypothetical) {
final Node node = map.get(e);
if (node == null) {
if (hypothetical) {
if (parentFunction != null) {
final List list = new ArrayList<>();
closure(parentFunction, e, list, new HashSet<>());
return list;
} else {
return ImmutableList.copyOf(strip(findParents(e)));
}
} else {
return null;
}
} else {
return strip(node.parentList);
}
}
private void closure(Function> generator, E e, List list,
Set set) {
for (E p : Objects.requireNonNull(generator.apply(e))) {
if (set.add(e)) {
if (map.containsKey(p)) {
list.add(p);
} else {
closure(generator, p, list, set);
}
}
}
}
public List getNonChildren() {
return strip(topNode.childList);
}
public List getNonParents() {
return strip(bottomNode.parentList);
}
@Override public void clear() {
map.clear();
assert topNode.parentList.isEmpty();
topNode.childList.clear();
topNode.childList.add(bottomNode);
assert bottomNode.childList.isEmpty();
bottomNode.parentList.clear();
bottomNode.parentList.add(topNode);
}
/**
* Returns a list of values in the set that are less-than a given value.
* The list is in topological order but order is otherwise
* non-deterministic.
*
* @param e Value
* @return Values less than given value
*/
public List getDescendants(E e) {
return descendants(e, true);
}
/** Returns a list, backed by a list of
* {@link org.apache.calcite.util.PartiallyOrderedSet.Node}s, that strips
* away the node and returns the element inside.
*
* @param Element type
*/
public static List strip(List> list) {
if (list.size() == 1
&& list.get(0).e == null) {
// If parent list contains top element, a node whose element is null,
// officially there are no parents.
// Similarly child list and bottom element.
return ImmutableList.of();
}
return Util.transform(list, node -> node.e);
}
/** Converts an iterable of nodes into the list of the elements inside.
* If there is one node whose element is null, it represents a list
* containing either the top or bottom element, so we return the empty list.
*
* @param Element type
*/
private static ImmutableList strip(Iterable> iterable) {
final Iterator> iterator = iterable.iterator();
if (!iterator.hasNext()) {
return ImmutableList.of();
}
Node node = iterator.next();
if (!iterator.hasNext()) {
if (node.e == null) {
return ImmutableList.of();
} else {
return ImmutableList.of(node.e);
}
}
final ImmutableList.Builder builder = ImmutableList.builder();
for (;;) {
builder.add(node.e);
if (!iterator.hasNext()) {
return builder.build();
}
node = iterator.next();
}
}
/**
* Returns a list of values in the set that are less-than a given value.
* The list is in topological order but order is otherwise
* non-deterministic.
*
* @param e Value
* @return Values less than given value
*/
public List getAncestors(E e) {
return descendants(e, false);
}
private List descendants(E e, boolean up) {
Node node = map.get(e);
final Collection> c;
if (node == null) {
c = up ? findChildren(e) : findParents(e);
} else {
c = up ? node.childList : node.parentList;
}
if (c.size() == 1 && c.iterator().next().e == null) {
return Collections.emptyList();
}
final Deque> deque = new ArrayDeque<>(c);
final Set> seen = new HashSet<>();
final List list = new ArrayList<>();
while (!deque.isEmpty()) {
Node node1 = deque.pop();
list.add(node1.e);
for (Node child : up ? node1.childList : node1.parentList) {
if (child.e == null) {
// Node is top or bottom.
break;
}
if (seen.add(child)) {
deque.add(child);
}
}
}
return list;
}
/**
* Holds a value, its parent nodes, and child nodes.
*
* We deliberately do not override {@link #hashCode} or
* {@link #equals(Object)}. A canonizing map ensures that within a
* given PartiallyOrderedSet, two nodes are identical if and only if they
* contain the same value.
*
* @param Element type
*/
private static class Node {
final List> parentList = new ArrayList<>();
final List> childList = new ArrayList<>();
final E e;
Node(E e) {
this.e = e;
}
@Override public String toString() {
return e.toString();
}
}
/**
* Subclass of Node for top/bottom nodes. Improves readability when
* debugging.
*
* @param Element type
*/
private static class TopBottomNode extends Node {
private final String description;
TopBottomNode(boolean top) {
super(null);
this.description = top ? "top" : "bottom";
}
@Override public String toString() {
return description;
}
}
/**
* Ordering relation.
*
* To obey the constraints of the partially-ordered set, the function
* must be consistent with the reflexive, anti-symmetric, and transitive
* properties required by a partially ordered set.
*
* For instance, if {@code ordering(foo, foo)} returned false for any
* not-null value of foo, it would violate the reflexive property.
*
* If an ordering violates any of these required properties, the behavior
* of a {@link PartiallyOrderedSet} is unspecified. (But mayhem is
* likely.)
*
* @param Element type
*/
public interface Ordering {
/**
* Returns whether element e1 is ≤ e2 according to
* the relation that defines a partially-ordered set.
*
* @param e1 Element 1
* @param e2 Element 2
* @return Whether element 1 is ≤ element 2
*/
boolean lessThan(E e1, E e2);
}
}
// End PartiallyOrderedSet.java