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

io.takamaka.code.util.StorageTreeMap Maven / Gradle / Ivy

The newest version!
/*
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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
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 map from storage keys to (possibly {@code null}) storage values,
 * that can be kept in storage. By iterating on this object, one gets
 * the key/value pairs of the map, in increasing key 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 values. The map can be kept in storage. Keys
 * and values must have types allowed in storage. Keys 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 class represents an ordered symbol table of generic key-value pairs.
 * It supports the usual put, get, contains,
 * remove, size, and is-empty methods.
 * It also provides ordered methods for finding the minimum,
 * maximum, floor, and ceiling.
 * A symbol table implements the associative array abstraction:
 * when associating a value with a key that is already in the symbol table,
 * the convention is to replace the old value with the new value.
 * 

* This implementation uses a left-leaning red-black BST. It requires that * the key type is a storage class or implements the {@code Comparable} interface * and in such a case calls the * {@code compareTo()} method to compare two keys. It does not call neither * {@code equals()} nor {@code hashCode()}. * The put, 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 keys * @param the type of the values */ public class StorageTreeMap extends Storage implements StorageMap { /** * The root of the tree. */ private Node root; /** * Builds an empty map. */ public StorageTreeMap() {} /** * Creates a map initialized to the same bindings as the given parent map. * * @param parent the parent map */ public StorageTreeMap(Map parent) { parent.forEach(this::put); } /** * Yields a snapshot of the given map. * * @param parent the map */ private StorageTreeMap(StorageTreeMap parent) { this.root = parent.root; } private void mkRootBlack() { if (isRed(root)) root = Node.mkBlack(root.key, root.value, root.size, root.left, root.right); } private void mkRootRed() { if (isBlack(root)) root = Node.mkRed(root.key, root.value, root.size, root.left, root.right); } /** * A node of the binary search tree that implements the map. */ private abstract static class Node extends Storage implements Entry { protected final K key; // always non-null protected final V value; // possibly null protected final Node left, right; /** * Count of the subtree nodes. */ protected final int size; private Node(K key, V value, int size, Node left, Node right) { this.key = key; this.value = value; this.size = size; this.left = left; this.right = right; } protected static Node mkBlack(K key, V value, int size, Node left, Node right) { return new BlackNode<>(key, value, size, left, right); } protected static Node mkRed(K key, V value, int size, Node left, Node right) { return new RedNode<>(key, value, size, left, right); } protected static Node mkRed(K key, V value) { return new RedNode<>(key, value, 1, null, null); } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @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() { // assert isRed(this) && isBlack(left) && isBlack(left.left); Node h = flipColors(); return isRed(h.right.left) ? h.setRight(h.right.rotateRight()).rotateLeft().flipColors() : h; } private Node moveRedRight() { // assert isRed(this) && isBlack(right) && isBlack(right.left); 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(K key, V value, int size, Node left, Node right) { super(key, value, size, left, right); } @Override protected Node fixSize() { return mkRed(key, value, size(left) + size(right) + 1, left, right); } @Override protected Node flipColor() { return mkBlack(key, value, size, left, right); } @Override protected Node rotateLeft() { final Node x = right; Node newThis = mkRed(key, value, size(x.left) + size(left) + 1, left, x.left); return mkRed(x.key, x.value, size, newThis, x.right); } @Override protected Node rotateRight() { // assert isRed(left); final Node x = left; Node newThis = mkRed(key, value, size(x.right) + size(right) + 1, x.right, right); return mkRed(x.key, x.value, size, x.left, newThis); } @Override protected Node setValue(V value) { return mkRed(key, value, size, left, right); } @Override protected Node setLeft(Node left) { return mkRed(key, value, size, left, right); } @Override protected Node setRight(Node right) { return mkRed(key, value, size, left, right); } @Override protected Node flipColors() { // h must have opposite color of its two children // assert (h != null) && (h.left != null) && (h.right != null); // assert (isBlack(h) && isRed(h.left) && isRed(h.right)) // || (isRed(h) && isBlack(h.left) && isBlack(h.right)); return mkBlack(key, value, size, left.flipColor(), right.flipColor()); } } private static class BlackNode extends Node { private BlackNode(K key, V value, int size, Node left, Node right) { super(key, value, size, left, right); } @Override protected Node fixSize() { return mkBlack(key, value, size(left) + size(right) + 1, left, right); } @Override protected Node flipColor() { return mkRed(key, value, size, left, right); } @Override protected Node rotateLeft() { final Node x = right; Node newThis = mkRed(key, value, size(x.left) + size(left) + 1, left, x.left); return mkBlack(x.key, x.value, size, newThis, x.right); } @Override protected Node rotateRight() { // assert isRed(left); final Node x = left; Node newThis = mkRed(key, value, size(x.right) + size(right) + 1, x.right, right); return mkBlack(x.key, x.value, size, x.left, newThis); } @Override protected Node setValue(V value) { return mkBlack(key, value, size, left, right); } @Override protected Node setLeft(Node left) { return mkBlack(key, value, size, left, right); } @Override protected Node setRight(Node right) { return mkBlack(key, value, size, left, right); } @Override protected Node flipColors() { // this must have opposite color of its two children // assert (left != null) && (right != null); // assert (isBlack(this) && isRed(left) && isRed(right)) // || (isRed(this) && isBlack(left) && isBlack(right)); return mkRed(key, 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); } @Override public @View V get(Object key) { if (key == null) throw new IllegalArgumentException("key is null"); return get(root, key); } /** * Yields the value associated with the given key in subtree rooted at x; * * @param x the root of the subtree * @param key the key * @return the value. Yields {@code null} if the key is not found */ private static V get(Node x, Object key) { while (x != null) { int cmp = compareTo(key, x.key); if (cmp < 0) x = x.left; else if (cmp > 0) x = x.right; else return x.value; } return null; } @Override public @View V getOrDefault(Object key, V _default) { if (key == null) throw new IllegalArgumentException("key is null"); return getOrDefault(root, key, _default); } private static V getOrDefault(Node x, Object key, V _default) { while (x != null) { int cmp = compareTo(key, x.key); if (cmp < 0) x = x.left; else if (cmp > 0) x = x.right; else return x.value; } return _default; } @Override public V getOrDefault(Object key, Supplier _default) { if (key == null) throw new IllegalArgumentException("key is null"); return getOrDefault(root, key, _default); } // value associated with the given key in subtree rooted at x; uses supplier if no such key is found private static V getOrDefault(Node x, Object key, Supplier _default) { while (x != null) { int cmp = compareTo(key, x.key); if (cmp < 0) x = x.left; else if (cmp > 0) x = x.right; else return x.value; } return _default.get(); } @Override public @View boolean containsKey(Object key) { return containsKey(root, key); } /** * Checks if the given key is contained in the subtree rooted at x. * * @param x the root of the subtree * @param key the key * @return true if and only if that condition holds */ private static boolean containsKey(Node x, Object key) { while (x != null) { int cmp = compareTo(key, x.key); if (cmp < 0) x = x.left; else if (cmp > 0) x = x.right; else return true; } return false; } @Override public void put(K key, V value) { if (key == null) throw new IllegalArgumentException("key is null"); root = put(root, key, value); mkRootBlack(); } // insert the key-value pair in the subtree rooted at h private static Node put(Node h, K key, V value) { if (h == null) return Node.mkRed(key, value); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(put(h.left, key, value)); else if (cmp > 0) h = h.setRight(put(h.right, key, 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 both children of root are black, set root to red if (isBlack(root.left) && isBlack(root.right)) mkRootRed(); root = removeMin(root); if (!isEmpty()) mkRootBlack(); } // delete the key-value pair with the minimum key 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 both children of root are black, set root to red if (isBlack(root.left) && isBlack(root.right)) mkRootRed(); root = removeMax(root); if (!isEmpty()) mkRootBlack(); } // delete the key-value pair with the maximum key 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 key) { if (key == null) throw new IllegalArgumentException("key is null"); if (containsKey(key)) { // if both children of root are black, set root to red if (isBlack(root.left) && isBlack(root.right)) mkRootRed(); root = remove(root, key); if (!isEmpty()) mkRootBlack(); } } // delete the key-value pair with the given key rooted at h private static Node remove(Node h, Object key) { // assert get(h, key) != null; if (compareTo(key, h.key) < 0) { if (isBlack(h.left) && isBlack(h.left.left)) h = h.moveRedLeft(); h = h.setLeft(remove(h.left, key)); } else { if (isRed(h.left)) h = h.rotateRight(); if (compareTo(key, h.key) == 0 && (h.right == null)) return null; if (isBlack(h.right) && isBlack(h.right.left)) h = h.moveRedRight(); if (compareTo(key, h.key) == 0) { var x = min(h.right); if (isRed(h)) h = Node.mkRed(x.key, x.value, h.size, h.left, removeMin(h.right)); else h = Node.mkBlack(x.key, x.value, h.size, h.left, removeMin(h.right)); } else h = h.setRight(remove(h.right, key)); } return h.balance(); } @Override public @View K min() { if (isEmpty()) throw new NoSuchElementException("call to min() with empty symbol table"); return min(root).key; } // the smallest key in subtree rooted at x private static Node min(Node x) { // assert x != null; if (x.left == null) return x; else return min(x.left); } @Override public @View K max() { if (isEmpty()) throw new NoSuchElementException("call to max() with empty symbol table"); return max(root).key; } // the largest key in the subtree rooted at x private static Node max(Node x) { // assert x != null; if (x.right == null) return x; else return max(x.right); } @Override public @View K floorKey(K key) { if (key == null) throw new IllegalArgumentException("key is null"); if (isEmpty()) throw new NoSuchElementException(); var x = floorKey(root, key); if (x == null) throw new NoSuchElementException(); else return x.key; } // the largest key in the subtree rooted at x less than or equal to the given key private static Node floorKey(Node x, K key) { if (x == null) return null; int cmp = compareTo(key, x.key); if (cmp == 0) return x; if (cmp < 0) return floorKey(x.left, key); var t = floorKey(x.right, key); if (t != null) return t; else return x; } @Override public @View K ceilingKey(K key) { if (key == null) throw new IllegalArgumentException("key is null"); if (isEmpty()) throw new NoSuchElementException(); var x = ceilingKey(root, key); if (x == null) throw new NoSuchElementException(); else return x.key; } // the smallest key in the subtree rooted at x greater than or equal to the given key private static Node ceilingKey(Node x, K key) { if (x == null) return null; int cmp = compareTo(key, x.key); if (cmp == 0) return x; if (cmp > 0) return ceilingKey(x.right, key); var t = ceilingKey(x.left, key); if (t != null) return t; else return x; } @Override public @View K select(int k) { if (k < 0 || k >= size()) throw new IllegalArgumentException("argument to select() is invalid: " + k); return select(root, k).key; } // the key of rank k in the subtree rooted at x private static Node select(Node x, int k) { // assert x != null; // assert k >= 0 && k < size(x); 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(K key) { if (key == null) throw new IllegalArgumentException("key is null"); return rank(key, root); } // number of keys less than key in the subtree rooted at x private static int rank(K key, Node x) { if (x == null) return 0; int cmp = compareTo(key, x.key); if (cmp < 0) return rank(key, x.left); else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right); else return size(x.left); } @Override public void update(K key, UnaryOperator how) { if (key == null) throw new IllegalArgumentException("key is null"); root = update(root, key, how); mkRootBlack(); } private static Node update(Node h, K key, UnaryOperator how) { if (h == null) return Node.mkRed(key, how.apply(null)); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(update(h.left, key, how)); else if (cmp > 0) h = h.setRight(update(h.right, key, how)); else h = h.setValue(how.apply(h.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 update(K key, V _default, UnaryOperator how) { if (key == null) throw new IllegalArgumentException("key is null"); root = update(root, key, _default, how); mkRootBlack(); } private static Node update(Node h, K key, V _default, UnaryOperator how) { if (h == null) return Node.mkRed(key, how.apply(_default)); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(update(h.left, key, _default, how)); else if (cmp > 0) h = h.setRight(update(h.right, key, _default, how)); else if (h.value == null) h = h.setValue(how.apply(_default)); else h = h.setValue(how.apply(h.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 update(K key, Supplier _default, UnaryOperator how) { if (key == null) throw new IllegalArgumentException("key is null"); root = update(root, key, _default, how); mkRootBlack(); } private static Node update(Node h, K key, Supplier _default, UnaryOperator how) { if (h == null) return Node.mkRed(key, how.apply(_default.get())); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(update(h.left, key, _default, how)); else if (cmp > 0) h = h.setRight(update(h.right, key, _default, how)); else if (h.value == null) h = h.setValue(how.apply(_default.get())); else h = h.setValue(how.apply(h.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 V putIfAbsent(K key, V value) { if (key == null) throw new IllegalArgumentException("key is null"); class PutIfAbsent { private V result; private Node putIfAbsent(Node h) { // not found: result remains null if (h == null) // not found return Node.mkRed(key, value); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(putIfAbsent(h.left)); else if (cmp > 0) h = h.setRight(putIfAbsent(h.right)); else if (h.value == null) // found but was bound to null: result remains null return h.setValue(value); else { // found and was bound to a non-null value result = h.value; return h; } // 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(); } } PutIfAbsent pia = new PutIfAbsent(); root = pia.putIfAbsent(root); mkRootBlack(); return pia.result; } @Override public V computeIfAbsent(K key, Supplier supplier) { if (key == null) throw new IllegalArgumentException("key is null"); class ComputeIfAbsent { private V result; private Node computeIfAbsent(Node h) { if (h == null) // not found return Node.mkRed(key, result = supplier.get()); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(computeIfAbsent(h.left)); else if (cmp > 0) h = h.setRight(computeIfAbsent(h.right)); else if (h.value == null) { // found but was bound to null h = h.setValue(supplier.get()); result = h.value; return h; } else { // found and was bound to a non-null value result = h.value; return h; } // 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(); } } ComputeIfAbsent cia = new ComputeIfAbsent(); root = cia.computeIfAbsent(root); mkRootBlack(); return cia.result; } @Override public V computeIfAbsent(K key, Function supplier) { if (key == null) throw new IllegalArgumentException("key is null"); class ComputeIfAbsent { private V result; private Node computeIfAbsent(Node h) { if (h == null) // not found return Node.mkRed(key, result = supplier.apply(key)); int cmp = compareTo(key, h.key); if (cmp < 0) h = h.setLeft(computeIfAbsent(h.left)); else if (cmp > 0) h = h.setRight(computeIfAbsent(h.right)); else if (h.value == null) { // found but was bound to null h = h.setValue(supplier.apply(key)); result = h.value; return h; } else { // found and was bound to a non-null value result = h.value; return h; } // 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(); } } ComputeIfAbsent cia = new ComputeIfAbsent(); root = cia.computeIfAbsent(root); mkRootBlack(); return cia.result; } @Override public void clear() { root = null; } @Override public Iterator> iterator() { return new StorageMapIterator<>(root); } private static class StorageMapIterator implements Iterator> { // the path under enumeration; it holds that the left children // have already been enumerated private final List> stack = new ArrayList<>(); private StorageMapIterator(Node root) { // initially, the stack contains the leftmost path of the tree for (var cursor = root; cursor != null; cursor = cursor.left) stack.add(cursor); } @Override public boolean hasNext() { return !stack.isEmpty(); } @Override public Entry next() { var topmost = stack.remove(stack.size() - 1); // we add the leftmost path of the right child of topmost for (var cursor = topmost.right; cursor != null; cursor = cursor.left) stack.add(cursor); return topmost; } } @Override public Stream> stream() { return StreamSupport.stream(spliterator(), false); } @Override public List keyList() { List keys = new ArrayList<>(); if (root != null) keyList(root, keys); return keys; } private static void keyList(Node x, List keys) { if (x.left != null) keyList(x.left, keys); keys.add(x.key); if (x.right != null) keyList(x.right, keys); } @Override public Stream keys() { return stream().map(Entry::getKey); } @Override public Stream values() { return stream().map(Entry::getValue); } @Override public StorageMapView view() { /** * A read-only view of a parent storage map. A view contains the same bindings * as the parent storage map, 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 map. */ @Exported class StorageMapViewImpl extends Storage implements StorageMapView { @Override public @View int size() { return StorageTreeMap.this.size(); } @Override public @View boolean isEmpty() { return StorageTreeMap.this.isEmpty(); } @Override public @View boolean containsKey(Object value) { return StorageTreeMap.this.containsKey(value); } @Override public Iterator> iterator() { return StorageTreeMap.this.iterator(); } @Override public V get(Object key) { return StorageTreeMap.this.get(key); } @Override public V getOrDefault(Object key, V _default) { return StorageTreeMap.this.getOrDefault(key, _default); } @Override public V getOrDefault(Object key, Supplier _default) { return StorageTreeMap.this.getOrDefault(key, _default); } @Override public K min() { return StorageTreeMap.this.min(); } @Override public K max() { return StorageTreeMap.this.max(); } @Override public K floorKey(K key) { return StorageTreeMap.this.floorKey(key); } @Override public K ceilingKey(K key) { return StorageTreeMap.this.ceilingKey(key); } @Override public K select(int k) { return StorageTreeMap.this.select(k); } @Override public int rank(K key) { return StorageTreeMap.this.rank(key); } @Override public Stream> stream() { return StorageTreeMap.this.stream(); } @Override public List keyList() { return StorageTreeMap.this.keyList(); } @Override public Stream keys() { return StorageTreeMap.this.keys(); } @Override public StorageMapView snapshot() { return StorageTreeMap.this.snapshot(); } @Override public Stream values() { return StorageTreeMap.this.values(); } } return new StorageMapViewImpl(); } @Override public StorageMapView snapshot() { return new StorageTreeMap<>(this).view(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy