All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.calcite.util.PartiallyOrderedSet Maven / Gradle / Ivy

There is a newer version: 1.17.0-flink-r3
Show newest version
/*
 * 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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy