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

org.pure4j.collections.PersistentArrayMap Maven / Gradle / Ivy

There is a newer version: 0.3.1
Show newest version
/**
 *   Copyright (c) Rich Hickey. All rights reserved.
 *   The use and distribution terms for this software are covered by the
 *   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
 *   which can be found in the file epl-v10.html at the root of this distribution.
 *   By using this software in any fashion, you are agreeing to be bound by
 * 	 the terms of this license.
 *   You must not remove this notice, or any other, from this software.
 **/

package org.pure4j.collections;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

import org.pure4j.Pure4J;
import org.pure4j.annotations.immutable.IgnoreImmutableTypeCheck;
import org.pure4j.annotations.pure.Enforcement;
import org.pure4j.annotations.pure.Pure;
import org.pure4j.annotations.pure.PureParameters;



/**
 * Simple implementation of persistent map on an array.
 * 
 * Note that instances of this class are constant values i.e. add/remove etc
 * return new values
 * 
 * Copies array on every change, so only appropriate for _very_small_ maps
 * 
 * null keys and values are ok, but you won't be able to distinguish a null
 * value via valAt - use contains/entryAt
 * 
 * @param  Key
 * @param  value
 */

public class PersistentArrayMap extends APersistentMap implements IMapIterable {

	@IgnoreImmutableTypeCheck
	private final Object[] array;
	static final int HASHTABLE_THRESHOLD = 16;

	private static final PersistentArrayMap EMPTY = new PersistentArrayMap();

	@Pure
	@PureParameters
	@SuppressWarnings("unchecked")
	static public  IPersistentMap create(Map other) {
		Pure4J.immutable(other);
		ITransientMap ret = (ITransientMap) EMPTY.asTransient();
		for (Entry e : other.entrySet()) {
			ret.put(e.getKey(), e.getValue());
		}
		return ret.persistent();
	}

	protected PersistentArrayMap() {
		this.array = new Object[] {};
	}

	@SuppressWarnings("unchecked")
	private IPersistentMap createHT(Object[] init) {
		return (IPersistentMap) PersistentHashMap.create(init);
	}

	@SafeVarargs
	static public  PersistentArrayMap createWithCheck(K... init) {
		for (int i = 0; i < init.length; i += 2) {
			for (int j = i + 2; j < init.length; j += 2) {
				if (equalKey(init[i], init[j]))
					throw new IllegalArgumentException("Duplicate key: "
							+ init[i]);
			}
		}
		return new PersistentArrayMap(init, true);
	}

	@SuppressWarnings("unchecked")
	static public  PersistentArrayMap createAsIfByAssoc(K[] init) {
		// If this looks like it is doing busy-work, it is because it
		// is achieving these goals: O(n^2) run time like
		// createWithCheck(), never modify init arg, and only
		// allocate memory if there are duplicate keys.
		int n = 0;
		for (int i = 0; i < init.length; i += 2) {
			boolean duplicateKey = false;
			for (int j = 0; j < i; j += 2) {
				if (equalKey(init[i], init[j])) {
					duplicateKey = true;
					break;
				}
			}
			if (!duplicateKey)
				n += 2;
		}
		if (n < init.length) {
			// Create a new shorter array with unique keys, and
			// the last value associated with each key. To behave
			// like assoc, the first occurrence of each key must
			// be used, since its metadata may be different than
			// later equal keys.
			Object[] nodups = new Object[n];
			int m = 0;
			for (int i = 0; i < init.length; i += 2) {
				boolean duplicateKey = false;
				for (int j = 0; j < m; j += 2) {
					if (equalKey(init[i], nodups[j])) {
						duplicateKey = true;
						break;
					}
				}
				if (!duplicateKey) {
					int j;
					for (j = init.length - 2; j >= i; j -= 2) {
						if (equalKey(init[i], init[j])) {
							break;
						}
					}
					nodups[m] = init[i];
					nodups[m + 1] = init[j + 1];
					m += 2;
				}
			}
			if (m != n)
				throw new IllegalArgumentException("Internal error: m=" + m);
			init = (K[]) nodups;
		}
		return new PersistentArrayMap(init, true);
	}

	/**
	 * Creates a private copy of init.
	 *
	 * @param init
	 *            {key1,val1,key2,val2,...}
	 */
	@Pure(Enforcement.NOT_PURE)
	public PersistentArrayMap(Object[] init) {
		this(init, true);
	}
	
	@Pure(Enforcement.NOT_PURE) // makes copy and is private.
	private PersistentArrayMap(Object[] init, boolean makeCopy) {
		if (makeCopy) {
			init = Arrays.copyOf(init, init.length);
		}
		this.array = init;
	}

	public int count() {
		return array.length / 2;
	}

	public boolean containsKey(Object key) {
		Pure4J.immutable(key);
		return indexOf(key) >= 0;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public IMapEntry entryAt(Object key) {
		Pure4J.immutable(key);
		int i = indexOf(key);
		if (i >= 0)
			return new MapEntry(array[i], array[i + 1]);
		return null;
	}

	@Pure(Enforcement.FORCE)	// performs array copy on own array
	public IPersistentMap assocEx(K key, V val) {
		Pure4J.immutable(key, val);
		int i = indexOf(key);
		Object[] newArray;
		if (i >= 0) {
			throw Util.runtimeException("Key already present");
		} else // didn't have key, grow
		{
			if (array.length > HASHTABLE_THRESHOLD)
				return createHT(array).assocEx(key, val);
			newArray = new Object[array.length + 2];
			if (array.length > 0)
				System.arraycopy(array, 0, newArray, 2, array.length);
			newArray[0] = key;
			newArray[1] = val;
		}
		return new PersistentArrayMap(newArray, false);
	}

	@Pure(Enforcement.FORCE)	// performs array copy on own array
	public IPersistentMap assoc(K key, V val) {
		Pure4J.immutable(key, val);
		int i = indexOf(key);
		Object[] newArray;
		if (i >= 0) // already have key, same-sized replacement
		{
			if (array[i + 1] == val) // no change, no op
				return this;
			newArray = array.clone();
			newArray[i + 1] = val;
		} else // didn't have key, grow
		{
			if (array.length > HASHTABLE_THRESHOLD)
				return ((IPersistentMap) createHT(array)).assoc(key, val);
			newArray = new Object[array.length + 2];
			if (array.length > 0)
				System.arraycopy(array, 0, newArray, 0, array.length);
			newArray[newArray.length - 2] = key;
			newArray[newArray.length - 1] = val;
		}
		return new PersistentArrayMap(newArray, false);
	}

	@Pure(Enforcement.FORCE)	// private array manipulation
	public IPersistentMap without(Object key) {
		Pure4J.immutable(key);
		int i = indexOf(key);
		if (i >= 0) // have key, will remove
		{
			int newlen = array.length - 2;
			if (newlen == 0)
				return empty();
			Object[] newArray = new Object[newlen];
			for (int s = 0, d = 0; s < array.length; s += 2) {
				if (!equalKey(array[s], key)) // skip removal key
				{
					newArray[d] = array[s];
					newArray[d + 1] = array[s + 1];
					d += 2;
				}
			}
			return new PersistentArrayMap(newArray, false);
		}
		// don't have key, no op
		return this;
	}

	@SuppressWarnings("unchecked")
	public IPersistentMap empty() {
		return (IPersistentMap) EMPTY;
	}
	
	@SuppressWarnings("unchecked")
	@Pure
	public static  PersistentArrayMap emptyMap() {
		return (PersistentArrayMap) EMPTY;
	}

	@SuppressWarnings("unchecked")
	final public V get(Object key, V notFound) {
		Pure4J.immutable(key, notFound);
		int i = indexOf(key);
		if (i >= 0)
			return (V) array[i + 1];
		return notFound;
	}

	public V get(Object key) {
		Pure4J.immutable(key);
		return get(key, null);
	}

	public int capacity() {
		return count();
	}

	@Pure(Enforcement.FORCE)
	private int indexOfObject(Object key) {
		for (int i = 0; i < array.length; i += 2) {
			if (Util.equals(key, array[i]))
				return i;
		}
		return -1;
	}

	@Pure(Enforcement.FORCE)	// since it's private, and doesn't modify
	private int indexOf(Object key) {
		return indexOfObject(key);
	}

	static boolean equalKey(Object k1, Object k2) {
		return Util.equals(k1, k2);
	}

	public Iterator> iterator() {
		return new Iter(array);
	}

	public IPureIterator keyIterator() {
		return KeySeq.create(seq()).iterator();
	}

	public IPureIterator valIterator() {
		return ValSeq.create(seq()).iterator();
	}

	public ISeq> seq() {
		if (array.length > 0)
			return new Seq(array, 0);
		return null;
	}

	static class Seq extends ASeq> implements Counted {
		final Object[] array;
		final int i;

		Seq(Object[] array, int i) {
			this.array = array;
			this.i = i;
		}

		@SuppressWarnings("unchecked")
		public Entry first() {
			return new MapEntry((K)array[i], (V)array[i + 1]);
		}

		public ISeq> next() {
			if (i + 2 < array.length)
				return new Seq(array, i + 2);
			return null;
		}

		public int count() {
			return (array.length - i) / 2;
		}
	}

	static class Iter implements IPureIterator> {
		Object[] array;
		int i;

		// for iterator
		Iter(Object[] array) {
			this(array, -2);
		}

		// for entryAt
		Iter(Object[] array, int i) {
			this.array = array;
			this.i = i;
		}

		public boolean hasNext() {
			return i < array.length - 2;
		}

		@SuppressWarnings("unchecked")
		public Entry next() {
			i += 2;
			return new MapEntry((K) array[i], (V) array[i + 1]);
		}

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

	}

	public ITransientMap asTransient() {
		return new TransientHashMap(this);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy