io.takamaka.code.util.StorageTreeSet Maven / Gradle / Ivy
Show all versions of io-takamaka-code Show documentation
/*
Copyright 2021 Fausto Spoto
Licensed 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 io.takamaka.code.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import io.takamaka.code.lang.Exported;
import io.takamaka.code.lang.Storage;
import io.takamaka.code.lang.View;
/**
* A sorted set of (non-{@code null}) storage values,
* that can be kept in storage. By iterating on this object, one gets
* the values in the set, in increasing order.
*
* This code is derived from Sedgewick and Wayne's code for
* red-black trees, with some adaptation. It implements an associative
* map from keys to a present/missing mark. The map can be kept in storage.
* Values must have types allowed in storage. They are kept in
* comparable order, if they implement {@link java.lang.Comparable}.
* Otherwise, they must extend {@link io.takamaka.code.lang.Storage} and
* are kept in storage reference order. This implementation does not call
* {@code equals()} nor {@code hashCode()}.
*
* This class represents an ordered set of values.
* It supports the usual add, contains,
* remove, size, and is-empty methods.
* It also provides ordered methods for finding the minimum,
* maximum, floor, and ceiling.
*
* The add, contains, delete, minimum,
* maximum, ceiling, and floor operations each take
* logarithmic time in the worst case, if the tree becomes unbalanced.
* The size, and is-empty operations take constant time.
* Construction takes constant time.
*
* For additional documentation, see Section 3.3 of
* Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
* @param the type of the values
*/
public class StorageTreeSet extends Storage implements StorageSet {
/**
* The root of the tree.
*/
private Node root;
/**
* Builds an empty set.
*/
public StorageTreeSet() {}
/**
* Creates a set initialized to the same elements as the given parent collection.
*
* @param parent the parent collection
*/
public StorageTreeSet(Collection extends V> parent) {
parent.forEach(this::add);
}
/**
* Yields a snapshot of the given set.
*
* @param parent the map
*/
private StorageTreeSet(StorageTreeSet parent) {
this.root = parent.root;
}
private void mkRootBlack() {
if (isRed(root))
root = Node.mkBlack(root.value, root.size, root.left, root.right);
}
private void mkRootRed() {
if (isBlack(root))
root = Node.mkRed(root.value, root.size, root.left, root.right);
}
/**
* A node of the binary search tree that implements the set.
*/
private abstract static class Node extends Storage {
protected final V value; // never null
protected final Node left, right;
/**
* Count of the subtree nodes.
*/
protected final int size;
private Node(V value, int size, Node left, Node right) {
this.value = value;
this.size = size;
this.left = left;
this.right = right;
}
protected static Node mkBlack(V value, int size, Node left, Node right) {
return new BlackNode<>(value, size, left, right);
}
protected static Node mkRed(V value, int size, Node left, Node right) {
return new RedNode<>(value, size, left, right);
}
protected static Node mkRed(V value) {
return new RedNode<>(value, 1, null, null);
}
@Override
public int hashCode() { // unused, but needed to satisfy white-listing for addition of Nodes inside Java collections
return 42;
}
protected abstract Node setValue(V value);
protected abstract Node setLeft(Node left);
protected abstract Node setRight(Node right);
protected abstract Node rotateRight();
protected abstract Node rotateLeft();
protected abstract Node flipColors();
protected abstract Node fixSize();
protected abstract Node flipColor();
private Node moveRedLeft() {
Node h = flipColors();
return isRed(h.right.left) ? h.setRight(h.right.rotateRight()).rotateLeft().flipColors() : h;
}
private Node moveRedRight() {
Node h = flipColors();
return isRed(h.left.left) ? h.rotateRight().flipColors() : h;
}
// restore red-black tree invariant
private Node balance() {
Node h = this;
if (isRed(h.right)) h = h.rotateLeft();
if (isRed(h.left) && isRed(h.left.left)) h = h.rotateRight();
if (isRed(h.left) && isRed(h.right)) h = h.flipColors();
return h.fixSize();
}
}
private static class RedNode extends Node {
private RedNode(V value, int size, Node left, Node right) {
super(value, size, left, right);
}
@Override
protected Node fixSize() {
return mkRed(value, size(left) + size(right) + 1, left, right);
}
@Override
protected Node flipColor() {
return mkBlack(value, size, left, right);
}
@Override
protected Node rotateLeft() {
final Node x = right;
Node newThis = mkRed(value, size(x.left) + size(left) + 1, left, x.left);
return mkRed(x.value, size, newThis, x.right);
}
@Override
protected Node rotateRight() {
// assert isRed(left);
final Node x = left;
Node newThis = mkRed(value, size(x.right) + size(right) + 1, x.right, right);
return mkRed(x.value, size, x.left, newThis);
}
@Override
protected Node setValue(V value) {
return mkRed(value, size, left, right);
}
@Override
protected Node setLeft(Node left) {
return mkRed(value, size, left, right);
}
@Override
protected Node setRight(Node right) {
return mkRed(value, size, left, right);
}
@Override
protected Node flipColors() {
return mkBlack(value, size, left.flipColor(), right.flipColor());
}
}
private static class BlackNode extends Node {
private BlackNode(V value, int size, Node left, Node right) {
super(value, size, left, right);
}
@Override
protected Node fixSize() {
return mkBlack(value, size(left) + size(right) + 1, left, right);
}
@Override
protected Node flipColor() {
return mkRed(value, size, left, right);
}
@Override
protected Node rotateLeft() {
final Node x = right;
Node newThis = mkRed(value, size(x.left) + size(left) + 1, left, x.left);
return mkBlack(x.value, size, newThis, x.right);
}
@Override
protected Node rotateRight() {
final Node x = left;
Node newThis = mkRed(value, size(x.right) + size(right) + 1, x.right, right);
return mkBlack(x.value, size, x.left, newThis);
}
@Override
protected Node setValue(V value) {
return mkBlack(value, size, left, right);
}
@Override
protected Node setLeft(Node left) {
return mkBlack(value, size, left, right);
}
@Override
protected Node setRight(Node right) {
return mkBlack(value, size, left, right);
}
@Override
protected Node flipColors() {
return mkRed(value, size, left.flipColor(), right.flipColor());
}
}
/**
* Determines if the given node is red.
*
* @param x the node
* @return true if and only if {@code x} is red
*/
private static boolean isRed(Node x) {
return x instanceof RedNode>;
}
/**
* Determines if the given node is black.
*
* @param x the node
* @return true if and only if {@code x} is black
*/
private static boolean isBlack(Node x) {
return x == null || x instanceof BlackNode>;
}
/**
* Yields the number of nodes in the subtree rooted at x.
*
* @param x the root of the subtree
* @return the number of nodes. Yields 0 if {@code x} is {@code null}
*/
private static int size(Node x) {
return x == null ? 0 : x.size;
}
@Override
public @View int size() {
return size(root);
}
@Override
public @View boolean isEmpty() {
return root == null;
}
@SuppressWarnings("unchecked")
private static int compareTo(K key1, K key2) {
if (key1 instanceof Comparable>)
return ((Comparable) key1).compareTo(key2);
else
return ((Storage) key1).compareByStorageReference((Storage) key2);
}
/**
* Determines if the given subtree contains the given value.
*
* @param x the root of the subtree
* @param value the value
* @return true if and only if that condition holds
*/
private static boolean contains(Node x, Object value) {
while (x != null) {
int cmp = compareTo(value, x.value);
if (cmp < 0) x = x.left;
else if (cmp > 0) x = x.right;
else return true;
}
return false;
}
@Override
public @View boolean contains(Object value) {
if (value == null) throw new IllegalArgumentException("value is null");
return contains(root, value);
}
@Override
public void add(V value) {
if (value == null) throw new IllegalArgumentException("value is null");
root = put(root, value);
mkRootBlack();
}
// insert the value in the subtree rooted at h
private static Node put(Node h, V value) {
if (h == null) return Node.mkRed(value);
int cmp = compareTo(value, h.value);
if (cmp < 0) h = h.setLeft(put(h.left, value));
else if (cmp > 0) h = h.setRight(put(h.right, value));
else h = h.setValue(value);
// fix-up any right-leaning links
if (isRed(h.right) && isBlack(h.left)) h = h.rotateLeft();
if (isRed(h.left) && isRed(h.left.left)) h = h.rotateRight();
if (isRed(h.left) && isRed(h.right)) h = h.flipColors();
return h.fixSize();
}
@Override
public void removeMin() {
if (isEmpty()) throw new NoSuchElementException();
if (isBlack(root.left) && isBlack(root.right))
mkRootRed();
root = removeMin(root);
if (!isEmpty()) mkRootBlack();
}
// removes the minimum value in the tree rooted at h
private static Node removeMin(Node h) {
if (h.left == null)
return null;
if (isBlack(h.left) && isBlack(h.left.left))
h = h.moveRedLeft();
return h.setLeft(removeMin(h.left)).balance();
}
@Override
public void removeMax() {
if (isEmpty()) throw new NoSuchElementException();
if (isBlack(root.left) && isBlack(root.right))
mkRootRed();
root = removeMax(root);
if (!isEmpty()) mkRootBlack();
}
// delete the maximum value in the tree rooted at h
private static Node removeMax(Node h) {
if (isRed(h.left))
h = h.rotateRight();
if (h.right == null)
return null;
if (isBlack(h.right) && isBlack(h.right.left))
h = h.moveRedRight();
return h.setRight(removeMax(h.right)).balance();
}
@Override
public void remove(Object value) {
if (value == null) throw new IllegalArgumentException("value is null");
if (contains(value)) {
if (isBlack(root.left) && isBlack(root.right))
mkRootRed();
root = remove(root, value);
if (!isEmpty()) mkRootBlack();
}
}
// delete the given value from the tree rooted at h
private static Node remove(Node h, Object value) {
if (compareTo(value, h.value) < 0) {
if (isBlack(h.left) && isBlack(h.left.left))
h = h.moveRedLeft();
h = h.setLeft(remove(h.left, value));
}
else {
if (isRed(h.left))
h = h.rotateRight();
if (compareTo(value, h.value) == 0 && (h.right == null))
return null;
if (isBlack(h.right) && isBlack(h.right.left))
h = h.moveRedRight();
if (compareTo(value, h.value) == 0) {
Node x = min(h.right);
if (isRed(h))
h = Node.mkRed(x.value, h.size, h.left, removeMin(h.right));
else
h = Node.mkBlack(x.value, h.size, h.left, removeMin(h.right));
}
else
h = h.setRight(remove(h.right, value));
}
return h.balance();
}
@Override
public @View V min() {
if (isEmpty()) throw new NoSuchElementException("call to min() on an empty set");
return min(root).value;
}
// the smallest value in subtree rooted at x
private static Node min(Node x) {
if (x.left == null) return x;
else return min(x.left);
}
@Override
public @View V max() {
if (isEmpty()) throw new NoSuchElementException("call to max() on an empty set");
return max(root).value;
}
// the largest value in the subtree rooted at x
private static Node max(Node x) {
if (x.right == null) return x;
else return max(x.right);
}
@Override
public @View V floorKey(Object value) {
if (value == null) throw new IllegalArgumentException("value is null");
if (isEmpty()) throw new NoSuchElementException();
Node x = floorKey(root, value);
if (x == null) throw new NoSuchElementException();
else return x.value;
}
// the largest value in the subtree rooted at x less than or equal to the given value
private static Node floorKey(Node x, Object value) {
if (x == null) return null;
int cmp = compareTo(value, x.value);
if (cmp == 0) return x;
if (cmp < 0) return floorKey(x.left, value);
Node t = floorKey(x.right, value);
if (t != null) return t;
else return x;
}
@Override
public @View V ceilingKey(Object value) {
if (value == null) throw new IllegalArgumentException("value is null");
if (isEmpty()) throw new NoSuchElementException();
Node x = ceilingKey(root, value);
if (x == null) throw new NoSuchElementException();
else return x.value;
}
// the smallest value in the subtree rooted at x greater than or equal to the given value
private static Node ceilingKey(Node x, Object value) {
if (x == null) return null;
int cmp = compareTo(value, x.value);
if (cmp == 0) return x;
if (cmp > 0) return ceilingKey(x.right, value);
Node t = ceilingKey(x.left, value);
if (t != null) return t;
else return x;
}
@Override
public @View V select(int k) {
if (k < 0 || k >= size()) throw new IllegalArgumentException("argument to select() is invalid: " + k);
return select(root, k).value;
}
// yield the value of rank k in the subtree rooted at x
private static Node select(Node x, int k) {
int t = size(x.left);
if (t > k) return select(x.left, k);
else if (t < k) return select(x.right, k - t - 1);
else return x;
}
@Override
public @View int rank(Object value) {
if (value == null) throw new IllegalArgumentException("value is null");
return rank(value, root);
}
// number of values less than value in the subtree rooted at x
private static int rank(Object value, Node x) {
if (x == null) return 0;
int cmp = compareTo(value, x.value);
if (cmp < 0) return rank(value, x.left);
else if (cmp > 0) return 1 + size(x.left) + rank(value, x.right);
else return size(x.left);
}
@Override
public Iterator iterator() {
return new StorageSetIterator<>(root);
}
private static class StorageSetIterator implements Iterator {
// the path under enumeration; it is always true that the left children
// have already been enumerated
private final List> stack = new ArrayList<>();
private StorageSetIterator(Node root) {
// initially, the stack contains the leftmost path of the tree
for (Node cursor = root; cursor != null; cursor = cursor.left)
stack.add(cursor);
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public V next() {
Node topmost = stack.remove(stack.size() - 1);
// we add the leftmost path of the right child of topmost
for (Node cursor = topmost.right; cursor != null; cursor = cursor.left)
stack.add(cursor);
return topmost.value;
}
}
@Override
public Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
@Override
public String toString() {
return stream().map(Objects::toString).collect(Collectors.joining(",", "[", "]"));
}
@Override
public StorageSetView view() {
/**
* A read-only view of a parent storage set. A view contains the same elements
* as the parent storage set, but does not include modification methods.
* Moreover, a view is exported, so that it can be safely divulged
* outside the store of a node. Calls to the view are simply forwarded to
* the parent set.
*/
@Exported
class StorageSetViewImpl extends Storage implements StorageSetView {
@Override
public @View int size() {
return StorageTreeSet.this.size();
}
@Override
public @View boolean isEmpty() {
return StorageTreeSet.this.isEmpty();
}
@Override
public @View boolean contains(Object value) {
return StorageTreeSet.this.contains(value);
}
@Override
public @View V min() {
return StorageTreeSet.this.min();
}
@Override
public @View V max() {
return StorageTreeSet.this.max();
}
@Override
public @View V floorKey(Object value) {
return StorageTreeSet.this.floorKey(value);
}
@Override
public @View V ceilingKey(Object value) {
return StorageTreeSet.this.ceilingKey(value);
}
@Override
public @View V select(int k) {
return StorageTreeSet.this.select(k);
}
@Override
public @View int rank(Object value) {
return StorageTreeSet.this.rank(value);
}
@Override
public String toString() {
return StorageTreeSet.this.toString();
}
@Override
public Iterator iterator() {
return StorageTreeSet.this.iterator();
}
@Override
public Stream stream() {
return StorageTreeSet.this.stream();
}
@Override
public StorageSetView snapshot() {
return StorageTreeSet.this.snapshot();
}
}
return new StorageSetViewImpl();
}
@Override
public StorageSetView snapshot() {
return new StorageTreeSet<>(this).view();
}
}