blob: 98f70bc07adb4bcc1323f7179813ef8dd3a93e96 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2008 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.lrparser.symboltable;
/**
* An immutable map, like you would find in a functional programming language.
*
* Inserting a new pair into the map leaves the original map untouched,
* instead a new map that contains the pair is returned. Therefore
* an assignment is needed to "modify" the map (just like with Strings).
*
* <code>
* myMap = myMap.insert(key,value);
* </code>
*
* There is no remove() method because it is not needed. In order to
* "delete" a pair from the map simply save a reference to an old version
* of the map and restore the map from that old reference. This makes
* "undo" operations trivial to implement.
*
* <code>
* FunctionalMap oldMap = myMap; // save a reference
* myMap = myMap.insert(key,value); // insert the pair into the map
* myMap = oldMap; // delete the pair from the map
* </code>
*
* This map is implemented as a red-black tree data structure,
* and is based on the implementation found at:
* http://www.eecs.usma.edu/webs/people/okasaki/jfp99.ps
*
* @author Mike Kucera
*/
public class FunctionalMap<K extends Comparable<K>, V> {
private static final boolean RED = true, BLACK = false;
private static class Node<K, V> {
final K key;
final V val;
Node<K, V> left;
Node<K, V> right;
boolean color;
public Node(K key, V val, boolean color, Node<K, V> left, Node<K, V> right) {
this.key = key;
this.val = val;
this.left = left;
this.right = right;
this.color = color;
}
@SuppressWarnings("nls")
@Override
public String toString() {
return "Node(" + key + "," + val + "," + (color ? "R" : "B") + ")";
}
}
private Node<K, V> root = null;
private FunctionalMap() {
// private constructor, use static factory method to instantiate
}
// factory method makes it cleaner to instantiate objects
public static <K extends Comparable<K>, V> FunctionalMap<K, V> emptyMap() {
return new FunctionalMap<>();
}
/**
* Returns a new map that contains the key-value pair.
* @throws NullPointerException if key is null
*/
public FunctionalMap<K, V> insert(K key, V val) {
if (key == null)
throw new NullPointerException();
FunctionalMap<K, V> newMap = new FunctionalMap<>();
newMap.root = insert(this.root, key, val);
newMap.root.color = BLACK; // force the root to be black
assert checkInvariants(newMap.root);
return newMap;
}
private Node<K, V> insert(Node<K, V> n, K key, V val) {
if (n == null)
return new Node<>(key, val, RED, null, null); // new nodes are always red
int c = key.compareTo(n.key);
if (c < 0)
return balance(n.key, n.val, n.color, insert(n.left, key, val), n.right);
else if (c > 0)
return balance(n.key, n.val, n.color, n.left, insert(n.right, key, val));
else // equal, create a new node that overwrites the old value
return new Node<>(key, val, n.color, n.left, n.right);
}
private Node<K, V> balance(K key, V val, boolean color, Node<K, V> left, Node<K, V> right) {
if (color == RED)
return new Node<>(key, val, color, left, right);
final Node<K, V> newLeft, newRight;
// now for the madness...
if (left != null && left.color == RED) {
if (left.left != null && left.left.color == RED) {
newLeft = new Node<>(left.left.key, left.left.val, BLACK, left.left.left, left.left.right);
newRight = new Node<>(key, val, BLACK, left.right, right);
return new Node<>(left.key, left.val, RED, newLeft, newRight);
}
if (left.right != null && left.right.color == RED) {
newLeft = new Node<>(left.key, left.val, BLACK, left.left, left.right.left);
newRight = new Node<>(key, val, BLACK, left.right.right, right);
return new Node<>(left.right.key, left.right.val, RED, newLeft, newRight);
}
}
if (right != null && right.color == RED) {
if (right.left != null && right.left.color == RED) {
newLeft = new Node<>(key, val, BLACK, left, right.left.left);
newRight = new Node<>(right.key, right.val, BLACK, right.left.right, right.right);
return new Node<>(right.left.key, right.left.val, RED, newLeft, newRight);
}
if (right.right != null && right.right.color == RED) {
newLeft = new Node<>(key, val, BLACK, left, right.left);
newRight = new Node<>(right.right.key, right.right.val, BLACK, right.right.left, right.right.right);
return new Node<>(right.key, right.val, RED, newLeft, newRight);
}
}
return new Node<>(key, val, BLACK, left, right);
}
/**
* Returns the value if it is in the map, null otherwise.
* @throws NullPointerException if key is null
*/
public V lookup(K key) {
if (key == null)
throw new NullPointerException();
// no need for recursion here
Node<K, V> n = root;
while (n != null) {
int x = key.compareTo(n.key); // throws NPE if key is null
if (x == 0)
return n.val;
n = (x < 0) ? n.left : n.right;
}
return null;
}
/**
* Returns true if there exists a mapping with the given key
* in this map.
* @throws NullPointerException if key is null
*/
public boolean containsKey(K key) {
if (key == null)
throw new NullPointerException();
// lookup uses an iterative algorithm
Node<K, V> n = root;
while (n != null) {
int x = key.compareTo(n.key); // throws NPE if key is null
if (x == 0)
return true;
n = (x < 0) ? n.left : n.right;
}
return false;
}
public boolean isEmpty() {
return root == null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder('[');
inorderPrint(root, sb);
sb.append(']');
return sb.toString();
}
private static <K, V> void inorderPrint(Node<K, V> n, StringBuilder sb) {
if (n == null)
return;
inorderPrint(n.left, sb);
if (sb.length() > 1)
sb.append(", ");//$NON-NLS-1$
sb.append(n.toString());
inorderPrint(n.right, sb);
}
void printStructure() {
if (root == null)
System.out.println("empty map"); //$NON-NLS-1$
else
printStructure(root, 0);
}
private static <K, V> void printStructure(Node<K, V> node, int level) {
for (int i = 0; i < level; i++)
System.out.print("--");//$NON-NLS-1$
if (node == null) {
System.out.println("null");//$NON-NLS-1$
} else if (node.right == null && node.left == null) {
System.out.println(node);
} else {
System.out.println(node);
printStructure(node.right, level + 1);
printStructure(node.left, level + 1);
}
}
private static <K, V> int depth(Node<K, V> node) {
if (node == null)
return 0;
return Math.max(depth(node.left), depth(node.right)) + 1;
}
/**
* Warning, this is a linear operation.
*/
public int size() {
return size(root);
}
private static <K, V> int size(Node<K, V> node) {
if (node == null)
return 0;
return size(node.left) + size(node.right) + 1;
}
/**********************************************************************************************
* Built-in testing
**********************************************************************************************/
private boolean checkInvariants(Node<K, V> n) {
// the number of black nodes on every path through the tree is the same
assertBalanced(n);
return true;
}
// not exactly sure if this is right
private int assertBalanced(Node<K, V> node) {
if (node == null)
return 1; // nulls are considered as black children
// both children of every red node are black
if (node.color == RED) {
assert node.left == null || node.left.color == BLACK;
assert node.right == null || node.right.color == BLACK;
}
int left = assertBalanced(node.left);
int right = assertBalanced(node.right);
assert left == right;
return left + (node.color == BLACK ? 1 : 0);
}
}