Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pure4j.collections.PersistentArrayMap Maven / Gradle / Ivy
/**
* 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);
}
}