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

org.semanticweb.elk.util.collections.ArrayHashSet Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * elk-reasoner
 * 
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 Oxford University Computing Laboratory
 * %%
 * 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.
 * #L%
 */
/**
 * @author Yevgeny Kazakov, May 17, 2011
 */
package org.semanticweb.elk.util.collections;

import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * hash sets using array and linear probing for resolving hash collision see [1]
 * p.526. Reuses some code from the implementation of {@link java.util.HashMap}.
 * 
 * [1] Donald E. Knuth, The Art of Computer Programming, Volume 3, Sorting and
 * Searching, Second Edition
 * 
 * @author Yevgeny Kazakov
 * @param 
 *            the type of the elements in this set
 * 
 */
public class ArrayHashSet extends AbstractSet implements Set,
		DirectAccess {

	/**
	 * The default initial capacity - MUST be a power of two.
	 */
	static final int DEFAULT_INITIAL_CAPACITY = 16;

	/**
	 * The maximum capacity, used if a higher value is implicitly specified by
	 * either of the constructors with arguments. MUST be a power of two <=
	 * 1<<30.
	 */
	static final int MAXIMUM_CAPACITY = 1 << 30;

	/**
	 * The table for the elements; the length MUST always be a power of two.
	 */
	transient E[] data;

	/**
	 * The number of elements contained in this set.
	 */
	transient int size;

	/**
	 * The next upper size value at which to stretch the table.
	 * 
	 * @serial
	 */
	int upperSize;

	/**
	 * The next lower size value at which to shrink the table.
	 * 
	 * @serial
	 */
	int lowerSize;

	@SuppressWarnings("unchecked")
	public ArrayHashSet(int initialCapacity) {
		if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal Capacity: "
					+ initialCapacity);
		if (initialCapacity > MAXIMUM_CAPACITY)
			initialCapacity = MAXIMUM_CAPACITY;
		// Find a power of 2 >= initialCapacity
		int capacity = 1;
		while (capacity < initialCapacity)
			capacity <<= 1;
		this.data = (E[]) new Object[capacity];
		this.size = 0;
		this.upperSize = computeUpperSize(capacity);
		this.lowerSize = computeLowerSize(capacity);
	}

	@SuppressWarnings("unchecked")
	public ArrayHashSet() {
		int capacity = DEFAULT_INITIAL_CAPACITY;
		this.data = (E[]) new Object[capacity];
		this.size = 0;
		this.upperSize = computeUpperSize(capacity);
		this.lowerSize = computeLowerSize(capacity);
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return size == 0;
	}

	/**
	 * Computes a maximum size of the table for a given capacity after which to
	 * stretch the table.
	 * 
	 * @param capacity
	 *            the capacity of the table.
	 * @return maximum size of the table for a given capacity after which to
	 *         stretch the table.
	 */
	static private int computeUpperSize(int capacity) {
		if (capacity > 64)
			return (3 * capacity) / 4; // max 75% filled
		else
			return capacity;
	}

	/**
	 * Computes a minimum size of the table for a given capacity after which to
	 * shrink the table.
	 * 
	 * @param capacity
	 *            the capacity of the table.
	 * @return minimum size of the table for a given capacity after which to
	 *         shrink the table
	 */
	static private int computeLowerSize(int capacity) {
		return capacity / 4;
	}

	static private int getIndex(Object o, int length) {
		return o.hashCode() & (length - 1);
	}

	@Override
	public boolean contains(Object o) {
		if (o == null)
			throw new NullPointerException();
		E[] data = this.data;
		int i = getIndex(o, data.length);
		int j = i; // for cycle detection
		for (;;) {
			Object probe = data[i];
			if (probe == null)
				return false;
			else if (o.equals(probe))
				return true;
			if (i == 0)
				i = data.length - 1;
			else
				i--;
			if (i == j) // full cycle
				return false;
		}
	}

	/**
	 * Adds the element to the set represented by given data array, if it did
	 * not contain there already.
	 * 
	 * @param data
	 *            the elements of the set
	 * @param e
	 *            the element to be added to the set
	 * @return true if the set has changed (the element is added),
	 *         false otherwise
	 */
	private boolean addElement(E[] data, E e) {
		int i = getIndex(e, data.length);
		for (;;) {
			Object probe = data[i];
			if (probe == null) {
				data[i] = e;
				return true;
			} else if (e.equals(probe))
				return false;
			if (i == 0)
				i = data.length - 1;
			else
				i--;
		}
	}

	/**
	 * Removes an element at position i of data shifting, if
	 * necessary, other elements so that all elements can be found by linear
	 * probing.
	 * 
	 * @param data
	 *            the array of the elements
	 * @param i
	 *            the position of data at which to delete the element
	 */
	private void shift(E[] data, int i) {
		int del = i; // the position at which the element is about to be deleted
		int j = i; // testing if the element at this position can still be
					// found by linear probing
		for (;;) {
			if (j == 0)
				j = data.length - 1;
			else
				j--;
			// invariant: interval [j, del[ contains non-null elements whose
			// index is in [j, del[
			if (j == del) {
				// we made a full cycle; no elements have to be shifted
				data[del] = null;
				return;
			}
			E test = data[j];
			if (test == null) {
				// no further elements to the left need to be shifted
				data[del] = null;
				return;
			}
			int k = getIndex(test, data.length);
			// check if k is in [j, del[
			if ((j < del) ? (j <= k) && (k < del) : (j <= k) || (k < del))
				// the index is in [j, del[, so the test element should not be
				// shifted
				continue;
			else {
				// copy the element to the position of deleted element and
				// start deleting its previous location
				data[del] = test;
				del = j;
				continue;
			}
		}
	}

	private boolean removeElement(E[] data, Object o) {
		int i = getIndex(o, data.length);
		int j = i; // for cycle detection
		for (;;) {
			Object probe = data[i];
			if (probe == null) {
				return false;
			} else if (o.equals(probe)) {
				shift(data, i);
				return true;
			}
			if (i == 0)
				i = data.length - 1;
			else
				i--;
			if (i == j) // full cycle
				return false;
		}
	}

	/**
	 * Increasing the capacity of the table
	 */
	private void stretch() {
		int oldCapacity = data.length;
		if (oldCapacity == MAXIMUM_CAPACITY)
			throw new IllegalArgumentException(
					"The set cannot grow beyond the capacity: "
							+ MAXIMUM_CAPACITY);
		E[] oldData = data;
		int newCapacity = oldCapacity << 1;
		@SuppressWarnings("unchecked")
		E[] newData = (E[]) new Object[newCapacity];
		for (int i = 0; i < oldCapacity; i++) {
			E e = oldData[i];
			if (e != null)
				addElement(newData, e);
		}
		this.data = newData;
		this.upperSize = computeUpperSize(newCapacity);
		this.lowerSize = computeLowerSize(newCapacity);
	}

	/**
	 * Decreasing the capacity of the table
	 */
	private void shrink() {
		int oldCapacity = data.length;
		if (oldCapacity == 1)
			return;
		E[] oldData = data;
		int newCapacity = oldCapacity >> 1;
		@SuppressWarnings("unchecked")
		E[] newData = (E[]) new Object[newCapacity];
		for (int i = 0; i < oldCapacity; i++) {
			E e = oldData[i];
			if (e != null)
				addElement(newData, e);
		}
		this.data = newData;
		this.upperSize = computeUpperSize(newCapacity);
		this.lowerSize = computeLowerSize(newCapacity);
	}

	@Override
	public boolean add(E e) {
		if (e == null)
			throw new NullPointerException();
		if (size == upperSize)
			stretch();
		boolean result = addElement(data, e);
		if (result)
			size++;
		return result;
	}

	@Override
	public boolean remove(Object o) {
		if (o == null)
			throw new NullPointerException();
		boolean result = removeElement(data, o);
		if (result)
			size--;
		if (size == lowerSize)
			shrink();
		return result;
	}

	@Override
	public boolean removeAll(Collection c) {
		boolean modified = false;
		for (Object o : c) {
			modified |= remove(o);
		}
		return modified;
	}

	@Override
	public Iterator iterator() {
		return new ElementIterator();
	}

	@SuppressWarnings("unchecked")
	@Override
	public void clear() {
		int capacity = data.length >> 2;
		if (capacity == 0)
			capacity = 1;
		size = 0;
		upperSize = computeUpperSize(capacity);
		lowerSize = computeLowerSize(capacity);
		this.data = (E[]) new Object[capacity];
	}

	@Override
	public String toString() {
		return Arrays.toString(toArray());
	}

	@Override
	public E[] getRawData() {
		return data;
	}

	private class ElementIterator implements Iterator {
		// copy of the data
		final E[] dataSnapshot;
		// expected size to check for concurrent modifications
		final int expectedSize;
		// current cursor
		int cursor;
		// next element to return
		E nextElement;

		ElementIterator() {
			this.expectedSize = size;
			this.dataSnapshot = data;
			cursor = 0;
			seekNext();
		}

		void seekNext() {
			while (cursor < dataSnapshot.length)
				if ((nextElement = dataSnapshot[cursor++]) != null)
					return;
			// no next element
			nextElement = null;
		}

		@Override
		public boolean hasNext() {
			return nextElement != null;
		}

		@Override
		public E next() {
			if (expectedSize != size)
				throw new ConcurrentModificationException();
			if (nextElement == null)
				throw new NoSuchElementException();
			E result = nextElement;
			seekNext();
			return result;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy