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

org.pcollections.IntTree Maven / Gradle / Ivy

There is a newer version: 3.3.8
Show newest version
/*
 * Copyright (c) 2008 Harold Cooper. All rights reserved.
 * Licensed under the MIT License.
 * See LICENSE file in the project root for full license information.
 */

package org.pcollections;

import java.io.Serializable;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Iterator;
import java.util.Map.Entry;

/**
 * A non-public utility class for persistent balanced tree maps with integer keys.
 *
 * 

To allow for efficiently increasing all keys above a certain value or decreasing all keys * below a certain value, the keys values are stored relative to their parent. This makes this map a * good backing for fast insertion and removal of indices in a vector. * *

This implementation is thread-safe except for its iterators. * *

Other than that, this tree is based on the Glasgow Haskell Compiler's Data.Map implementation, * which in turn is based on "size balanced binary trees" as described by: * *

Stephen Adams, "Efficient sets: a balancing act", Journal of Functional Programming * 3(4):553-562, October 1993, http://www.swiss.ai.mit.edu/~adams/BB/. * *

J. Nievergelt and E.M. Reingold, "Binary search trees of bounded balance", SIAM journal of * computing 2(1), March 1973. * * @author harold * @param */ class IntTree implements Serializable { private static final long serialVersionUID = 1L; // marker value: static final IntTree EMPTYNODE = new IntTree(); private final long key; // we use longs so relative keys can express all ints // (e.g. if this has key -10 and right has 'absolute' key MAXINT, // then its relative key is MAXINT+10 which overflows) // there might be some way to deal with this based on left-verse-right logic, // but that sounds like a mess. private final V value; // null value means this is empty node private final IntTree left, right; private final int size; private IntTree() { if (EMPTYNODE != null) throw new RuntimeException("empty constructor should only be used once"); size = 0; key = 0; value = null; left = null; right = null; } private IntTree(final long key, final V value, final IntTree left, final IntTree right) { this.key = key; this.value = value; this.left = left; this.right = right; size = 1 + left.size + right.size; } private IntTree withKey(final long newKey) { if (size == 0 || newKey == key) return this; return new IntTree(newKey, value, left, right); } Iterator> iterator() { return new EntryIterator(this); } int size() { return size; } boolean containsKey(final long key) { if (size == 0) return false; if (key < this.key) return left.containsKey(key - this.key); if (key > this.key) return right.containsKey(key - this.key); // otherwise key==this.key: return true; } V get(final long key) { if (size == 0) return null; if (key < this.key) return left.get(key - this.key); if (key > this.key) return right.get(key - this.key); // otherwise key==this.key: return value; } IntTree plus(final long key, final V value) { if (size == 0) return new IntTree(key, value, this, this); if (key < this.key) return rebalanced(left.plus(key - this.key, value), right); if (key > this.key) return rebalanced(left, right.plus(key - this.key, value)); // otherwise key==this.key, so we simply replace this, with no effect on balance: if (value == this.value) return this; return new IntTree(key, value, left, right); } IntTree minus(final long key) { if (size == 0) return this; if (key < this.key) return rebalanced(left.minus(key - this.key), right); if (key > this.key) return rebalanced(left, right.minus(key - this.key)); // otherwise key==this.key, so we are killing this node: if (left.size == 0) // we can just become right node // make key 'absolute': return right.withKey(right.key + this.key); if (right.size == 0) // we can just become left node return left.withKey(left.key + this.key); // otherwise replace this with the next key (i.e. the smallest key to the right): // TODO have minNode() instead of minKey to avoid having to call get() // TODO get node from larger subtree, i.e. if left.size>right.size use left.maxNode() // TODO have faster minusMin() instead of just using minus() long newKey = right.minKey() + this.key; // (right.minKey() is relative to this; adding this.key makes it 'absolute' // where 'absolute' really means relative to the parent of this) V newValue = right.get(newKey - this.key); // now that we've got the new stuff, take it out of the right subtree: IntTree newRight = right.minus(newKey - this.key); // lastly, make the subtree keys relative to newKey (currently they are relative to this.key): newRight = newRight.withKey((newRight.key + this.key) - newKey); // left is definitely not empty: IntTree newLeft = left.withKey((left.key + this.key) - newKey); return rebalanced(newKey, newValue, newLeft, newRight); } /** * Changes every key k>=key to k+delta. * *

This method will create an _invalid_ tree if delta<0 and the distance between the smallest * k>=key in this and the largest jIn other words, this method must not result in any change in the order of the keys in this, * since the tree structure is not being changed at all. */ IntTree changeKeysAbove(final long key, final int delta) { if (size == 0 || delta == 0) return this; if (this.key >= key) // adding delta to this.key changes the keys of _all_ children of this, // so we now need to un-change the children of this smaller than key, // all of which are to the left. note that we still use the 'old' relative key...: return new IntTree( this.key + delta, value, left.changeKeysBelow(key - this.key, -delta), right); // otherwise, doesn't apply yet, look to the right: IntTree newRight = right.changeKeysAbove(key - this.key, delta); if (newRight == right) return this; return new IntTree(this.key, value, left, newRight); } /** * Changes every key kThis method will create an _invalid_ tree if delta>0 and the distance between the largest * k=key in this is delta or less. * *

In other words, this method must not result in any overlap or change in the order of the * keys in this, since the tree _structure_ is not being changed at all. */ IntTree changeKeysBelow(final long key, final int delta) { if (size == 0 || delta == 0) return this; if (this.key < key) // adding delta to this.key changes the keys of _all_ children of this, // so we now need to un-change the children of this larger than key, // all of which are to the right. note that we still use the 'old' relative key...: return new IntTree( this.key + delta, value, left, right.changeKeysAbove(key - this.key, -delta)); // otherwise, doesn't apply yet, look to the left: IntTree newLeft = left.changeKeysBelow(key - this.key, delta); if (newLeft == left) return this; return new IntTree(this.key, value, newLeft, right); } // min key in this: private long minKey() { if (left.size == 0) return key; // make key 'absolute' (i.e. relative to the parent of this): return left.minKey() + this.key; } private IntTree rebalanced(final IntTree newLeft, final IntTree newRight) { if (newLeft == left && newRight == right) return this; // already balanced return rebalanced(key, value, newLeft, newRight); } private static final int OMEGA = 5; private static final int ALPHA = 2; // rebalance a tree that is off-balance by at most 1: private static IntTree rebalanced( final long key, final V value, final IntTree left, final IntTree right) { if (left.size + right.size > 1) { if (left.size >= OMEGA * right.size) { // rotate to the right IntTree ll = left.left, lr = left.right; if (lr.size < ALPHA * ll.size) // single rotation return new IntTree( left.key + key, left.value, ll, new IntTree(-left.key, value, lr.withKey(lr.key + left.key), right)); else { // double rotation: IntTree lrl = lr.left, lrr = lr.right; return new IntTree( lr.key + left.key + key, lr.value, new IntTree(-lr.key, left.value, ll, lrl.withKey(lrl.key + lr.key)), new IntTree( -left.key - lr.key, value, lrr.withKey(lrr.key + lr.key + left.key), right)); } } else if (right.size >= OMEGA * left.size) { // rotate to the left IntTree rl = right.left, rr = right.right; if (rl.size < ALPHA * rr.size) // single rotation return new IntTree( right.key + key, right.value, new IntTree(-right.key, value, left, rl.withKey(rl.key + right.key)), rr); else { // double rotation: IntTree rll = rl.left, rlr = rl.right; return new IntTree( rl.key + right.key + key, rl.value, new IntTree( -right.key - rl.key, value, left, rll.withKey(rll.key + rl.key + right.key)), new IntTree(-rl.key, right.value, rlr.withKey(rlr.key + rl.key), rr)); } } } // otherwise already balanced enough: return new IntTree(key, value, left, right); } //// entrySet().iterator() IMPLEMENTATION //// // TODO make this a ListIterator? private static final class EntryIterator implements Iterator> { private PStack> stack = ConsPStack.empty(); // path of nonempty nodes private int key = 0; // note we use _int_ here since this is a truly absolute key EntryIterator(final IntTree root) { gotoMinOf(root); } public boolean hasNext() { return stack.size() > 0; } public Entry next() { IntTree node = stack.get(0); final Entry result = new SimpleImmutableEntry(key, node.value); // find next node. // we've already done everything smaller, // so try least larger node: if (node.right.size > 0) // we can descend to the right gotoMinOf(node.right); else // can't descend to the right -- try ascending to the right while (true) { // find current node's least larger ancestor, if any key -= node.key; // revert to parent's key stack = stack.subList(1); // climb up to parent // if parent was larger than child or there was no parent, we're done: if (node.key < 0 || stack.size() == 0) break; // otherwise parent was smaller -- try its parent: node = stack.get(0); } return result; } public void remove() { throw new UnsupportedOperationException(); } // extend the stack to its least non-empty node: private void gotoMinOf(IntTree node) { while (node.size > 0) { stack = stack.plus(node); key += node.key; node = node.left; } } } }