
it.unimi.dsi.fastutil.objects.Object2DoubleArrayMap Maven / Gradle / Ivy
Show all versions of fastutil-core Show documentation
/*
* Copyright (C) 2007-2024 Sebastiano Vigna
*
* 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.
*/
package it.unimi.dsi.fastutil.objects;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.doubles.DoubleCollection;
import it.unimi.dsi.fastutil.doubles.AbstractDoubleCollection;
import it.unimi.dsi.fastutil.doubles.DoubleArrays;
/**
* A simple, brute-force implementation of a map based on two parallel backing arrays.
*
*
* The main purpose of this implementation is that of wrapping cleanly the brute-force approach to
* the storage of a very small number of pairs: just put them into two parallel arrays and scan
* linearly to find an item.
*/
public class Object2DoubleArrayMap extends AbstractObject2DoubleMap implements java.io.Serializable, Cloneable {
private static final long serialVersionUID = 1L;
/** The keys (valid up to {@link #size}, excluded). */
protected transient Object[] key;
/** The values (parallel to {@link #key}). */
protected transient double[] value;
/** The number of valid entries in {@link #key} and {@link #value}. */
protected int size;
/** Cached set of entries. */
protected transient FastEntrySet entries;
/** Cached set of keys. */
protected transient ObjectSet keys;
/** Cached collection of values. */
protected transient DoubleCollection values;
/**
* Creates a new empty array map with given key and value backing arrays. The resulting map will
* have as many entries as the given arrays.
*
*
* It is responsibility of the caller that the elements of {@code key} are distinct.
*
* @param key the key array.
* @param value the value array (it must have the same length as {@code key}).
*/
public Object2DoubleArrayMap(final Object[] key, final double[] value) {
this.key = key;
this.value = value;
size = key.length;
if (key.length != value.length) throw new IllegalArgumentException("Keys and values have different lengths (" + key.length + ", " + value.length + ")");
}
/**
* Creates a new empty array map.
*/
public Object2DoubleArrayMap() {
this.key = ObjectArrays.EMPTY_ARRAY;
this.value = DoubleArrays.EMPTY_ARRAY;
}
/**
* Creates a new empty array map of given capacity.
*
* @param capacity the initial capacity.
*/
public Object2DoubleArrayMap(final int capacity) {
this.key = new Object[capacity];
this.value = new double[capacity];
}
/**
* Creates a new empty array map copying the entries of a given map.
*
* @param m a map.
*/
public Object2DoubleArrayMap(final Object2DoubleMap m) {
this(m.size());
int i = 0;
for (Object2DoubleMap.Entry e : m.object2DoubleEntrySet()) {
key[i] = e.getKey();
value[i] = e.getDoubleValue();
i++;
}
size = i;
}
/**
* Creates a new empty array map copying the entries of a given map.
*
* @param m a map.
*/
public Object2DoubleArrayMap(final Map extends K, ? extends Double> m) {
this(m.size());
int i = 0;
for (Map.Entry extends K, ? extends Double> e : m.entrySet()) {
key[i] = (e.getKey());
value[i] = (e.getValue()).doubleValue();
i++;
}
size = i;
}
/**
* Creates a new array map with given key and value backing arrays, using the given number of
* elements.
*
*
* It is responsibility of the caller that the first {@code size} elements of {@code key} are
* distinct.
*
* @param key the key array.
* @param value the value array (it must have the same length as {@code key}).
* @param size the number of valid elements in {@code key} and {@code value}.
*/
public Object2DoubleArrayMap(final Object[] key, final double[] value, final int size) {
this.key = key;
this.value = value;
this.size = size;
if (key.length != value.length) throw new IllegalArgumentException("Keys and values have different lengths (" + key.length + ", " + value.length + ")");
if (size > key.length) throw new IllegalArgumentException("The provided size (" + size + ") is larger than or equal to the backing-arrays size (" + key.length + ")");
}
/**
* The entry class for an array map does not record key and value, but rather the position in the
* array of the corresponding entry. This is necessary so that calls to
* {@link java.util.Map.Entry#setValue(Object)} are reflected in the map
*/
private final class MapEntry implements Object2DoubleMap.Entry, Map.Entry, ObjectDoublePair {
// The array index this entry refers to, or -1 if this entry has been deleted.
int index;
MapEntry() {
}
MapEntry(final int index) {
this.index = index;
}
@Override
@SuppressWarnings("unchecked")
public K getKey() {
return (K)key[index];
}
@Override
@SuppressWarnings("unchecked")
public K left() {
return (K)key[index];
}
@Override
public double getDoubleValue() {
return value[index];
}
@Override
public double rightDouble() {
return value[index];
}
@Override
public double setValue(final double v) {
final double oldValue = value[index];
value[index] = v;
return oldValue;
}
@Override
public ObjectDoublePair right(final double v) {
value[index] = v;
return this;
}
/**
* {@inheritDoc}
*
* @deprecated Please use the corresponding type-specific method instead.
*/
@Deprecated
@Override
public Double getValue() {
return Double.valueOf(value[index]);
}
/**
* {@inheritDoc}
*
* @deprecated Please use the corresponding type-specific method instead.
*/
@Deprecated
@Override
public Double setValue(final Double v) {
return Double.valueOf(setValue((v).doubleValue()));
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(final Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry)o;
return java.util.Objects.equals(key[index], (e.getKey())) && (Double.doubleToLongBits(value[index]) == Double.doubleToLongBits((e.getValue()).doubleValue()));
}
@Override
public int hashCode() {
return ((key[index]) == null ? 0 : (key[index]).hashCode()) ^ it.unimi.dsi.fastutil.HashCommon.double2int(value[index]);
}
@Override
public String toString() {
return key[index] + "=>" + value[index];
}
}
private final class EntrySet extends AbstractObjectSet> implements FastEntrySet {
@Override
public ObjectIterator> iterator() {
return new ObjectIterator>() {
private MapEntry entry;
int curr = -1, next = 0;
@Override
public boolean hasNext() {
return next < size;
}
@Override
@SuppressWarnings("unchecked")
public Entry next() {
if (!hasNext()) throw new NoSuchElementException();
return entry = new MapEntry(curr = next++);
}
@Override
public void remove() {
if (curr == -1) throw new IllegalStateException();
curr = -1;
final int tail = size-- - next--;
System.arraycopy(key, next + 1, key, next, tail);
System.arraycopy(value, next + 1, value, next, tail);
entry.index = -1;
key[size] = null;
}
@Override
public int skip(int n) {
if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
n = Math.min(n, size - next);
next += n;
if (n != 0) curr = next - 1;
return n;
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(final Consumer super Object2DoubleMap.Entry> action) {
final int max = size;
while (next < max) {
entry = new MapEntry(curr = next++);
action.accept(entry);
}
}
};
}
@Override
public ObjectIterator> fastIterator() {
return new ObjectIterator>() {
private MapEntry entry = new MapEntry();
int next = 0, curr = -1;
@Override
public boolean hasNext() {
return next < size;
}
@Override
@SuppressWarnings("unchecked")
public Entry next() {
if (!hasNext()) throw new NoSuchElementException();
entry.index = curr = next++;
return entry;
}
@Override
public void remove() {
if (curr == -1) throw new IllegalStateException();
curr = -1;
final int tail = size-- - next--;
System.arraycopy(key, next + 1, key, next, tail);
System.arraycopy(value, next + 1, value, next, tail);
entry.index = -1;
key[size] = null;
}
@Override
public int skip(int n) {
if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
n = Math.min(n, size - next);
next += n;
if (n != 0) curr = next - 1;
return n;
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(final Consumer super Object2DoubleMap.Entry> action) {
final int max = size;
while (next < max) {
entry.index = curr = next++;
action.accept(entry);
}
}
};
}
// We already have to create an Entry object for each iteration, so the overhead from having
// skeletal implementations isn't significant.
final class EntrySetSpliterator extends ObjectSpliterators.EarlyBindingSizeIndexBasedSpliterator> implements ObjectSpliterator> {
EntrySetSpliterator(int pos, int maxPos) {
super(pos, maxPos);
}
@Override
public int characteristics() {
return ObjectSpliterators.SET_SPLITERATOR_CHARACTERISTICS | java.util.Spliterator.SUBSIZED | java.util.Spliterator.ORDERED;
}
@Override
@SuppressWarnings("unchecked")
protected final Object2DoubleMap.Entry get(int location) {
return new MapEntry(location);
}
@Override
protected final EntrySetSpliterator makeForSplit(int pos, int maxPos) {
return new EntrySetSpliterator(pos, maxPos);
}
}
@Override
public ObjectSpliterator> spliterator() {
return new EntrySetSpliterator(0, size);
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public void forEach(final Consumer super Object2DoubleMap.Entry> action) {
for (int i = 0, max = size; i < max; ++i) {
action.accept(new MapEntry(i));
}
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public void fastForEach(final Consumer super Object2DoubleMap.Entry> action) {
final MapEntry entry = new MapEntry();
for (int i = 0, max = size; i < max; ++i) {
entry.index = i;
action.accept(entry);
}
}
@Override
public int size() {
return size;
}
@Override
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) return false;
final Map.Entry, ?> e = (Map.Entry, ?>)o;
if (e.getValue() == null || !(e.getValue() instanceof Double)) return false;
final K k = ((K)e.getKey());
return Object2DoubleArrayMap.this.containsKey(k) && (Double.doubleToLongBits(Object2DoubleArrayMap.this.getDouble(k)) == Double.doubleToLongBits(((Double)(e.getValue())).doubleValue()));
}
@Override
@SuppressWarnings("unchecked")
public boolean remove(final Object o) {
if (!(o instanceof Map.Entry)) return false;
final Map.Entry, ?> e = (Map.Entry, ?>)o;
if (e.getValue() == null || !(e.getValue() instanceof Double)) return false;
final K k = ((K)e.getKey());
final double v = ((Double)(e.getValue())).doubleValue();
final int oldPos = Object2DoubleArrayMap.this.findKey(k);
if (oldPos == -1 || !(Double.doubleToLongBits(v) == Double.doubleToLongBits(Object2DoubleArrayMap.this.value[oldPos]))) return false;
final int tail = size - oldPos - 1;
System.arraycopy(Object2DoubleArrayMap.this.key, oldPos + 1, Object2DoubleArrayMap.this.key, oldPos, tail);
System.arraycopy(Object2DoubleArrayMap.this.value, oldPos + 1, Object2DoubleArrayMap.this.value, oldPos, tail);
Object2DoubleArrayMap.this.size--;
Object2DoubleArrayMap.this.key[size] = null;
return true;
}
}
@Override
public FastEntrySet object2DoubleEntrySet() {
if (entries == null) entries = new EntrySet();
return entries;
}
private int findKey(final Object k) {
final Object[] key = this.key;
for (int i = size; i-- != 0;) if (java.util.Objects.equals(key[i], k)) return i;
return -1;
}
@Override
public double getDouble(final Object k) {
final Object[] key = this.key;
for (int i = size; i-- != 0;) if (java.util.Objects.equals(key[i], k)) return value[i];
return defRetValue;
}
@Override
public int size() {
return size;
}
@Override
public void clear() {
final Object[] key = this.key;
for (int i = size; i-- != 0;) {
key[i] = null;
}
size = 0;
}
@Override
public boolean containsKey(final Object k) {
return findKey(k) != -1;
}
@Override
public boolean containsValue(double v) {
final double[] value = this.value;
for (int i = size; i-- != 0;) if ((Double.doubleToLongBits(value[i]) == Double.doubleToLongBits(v))) return true;
return false;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public double put(K k, double v) {
final int oldKey = findKey(k);
if (oldKey != -1) {
final double oldValue = value[oldKey];
value[oldKey] = v;
return oldValue;
}
if (size == key.length) {
final Object[] newKey = new Object[size == 0 ? 2 : size * 2];
final double[] newValue = new double[size == 0 ? 2 : size * 2];
for (int i = size; i-- != 0;) {
newKey[i] = key[i];
newValue[i] = value[i];
}
key = newKey;
value = newValue;
}
key[size] = k;
value[size] = v;
size++;
return defRetValue;
}
@Override
public double removeDouble(final Object k) {
final int oldPos = findKey(k);
if (oldPos == -1) return defRetValue;
final double oldValue = value[oldPos];
final int tail = size - oldPos - 1;
System.arraycopy(key, oldPos + 1, key, oldPos, tail);
System.arraycopy(value, oldPos + 1, value, oldPos, tail);
size--;
key[size] = null;
return oldValue;
}
private final class KeySet extends AbstractObjectSet {
@Override
public boolean contains(final Object k) {
return findKey(k) != -1;
}
@Override
public boolean remove(final Object k) {
final int oldPos = findKey(k);
if (oldPos == -1) return false;
final int tail = size - oldPos - 1;
System.arraycopy(key, oldPos + 1, key, oldPos, tail);
System.arraycopy(value, oldPos + 1, value, oldPos, tail);
size--;
Object2DoubleArrayMap.this.key[size] = null;
return true;
}
@Override
public ObjectIterator iterator() {
return new ObjectIterator() {
int pos = 0;
@Override
public boolean hasNext() {
return pos < size;
}
@Override
@SuppressWarnings("unchecked")
public K next() {
if (!hasNext()) throw new NoSuchElementException();
return (K)key[pos++];
}
@Override
public void remove() {
if (pos == 0) throw new IllegalStateException();
final int tail = size - pos;
System.arraycopy(key, pos, key, pos - 1, tail);
System.arraycopy(value, pos, value, pos - 1, tail);
size--;
pos--;
Object2DoubleArrayMap.this.key[size] = null;
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(final Consumer super K> action) {
final Object[] key = Object2DoubleArrayMap.this.key;
final int max = size;
while (pos < max) {
action.accept((K)key[pos++]);
}
}
// TODO either override skip or extend from AbstractIndexBasedIterator.
};
}
final class KeySetSpliterator extends ObjectSpliterators.EarlyBindingSizeIndexBasedSpliterator implements ObjectSpliterator {
KeySetSpliterator(int pos, int maxPos) {
super(pos, maxPos);
}
@Override
public int characteristics() {
return ObjectSpliterators.SET_SPLITERATOR_CHARACTERISTICS | java.util.Spliterator.SUBSIZED | java.util.Spliterator.ORDERED;
}
@Override
@SuppressWarnings("unchecked")
protected final K get(int location) {
return (K)key[location];
}
@Override
protected final KeySetSpliterator makeForSplit(int pos, int maxPos) {
return new KeySetSpliterator(pos, maxPos);
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(final Consumer super K> action) {
final Object[] key = Object2DoubleArrayMap.this.key;
final int max = size;
while (pos < max) {
action.accept((K)key[pos++]);
}
}
}
@Override
public ObjectSpliterator spliterator() {
return new KeySetSpliterator(0, size);
}
@Override
@SuppressWarnings("unchecked")
public void forEach(Consumer super K> action) {
final Object[] key = Object2DoubleArrayMap.this.key;
for (int i = 0, max = size; i < max; ++i) {
action.accept((K)key[i]);
}
}
@Override
public int size() {
return size;
}
@Override
public void clear() {
Object2DoubleArrayMap.this.clear();
}
}
@Override
public ObjectSet keySet() {
if (keys == null) keys = new KeySet();
return keys;
}
private final class ValuesCollection extends AbstractDoubleCollection {
@Override
public boolean contains(final double v) {
return containsValue(v);
}
@Override
public it.unimi.dsi.fastutil.doubles.DoubleIterator iterator() {
return new it.unimi.dsi.fastutil.doubles.DoubleIterator() {
int pos = 0;
@Override
public boolean hasNext() {
return pos < size;
}
@Override
public double nextDouble() {
if (!hasNext()) throw new NoSuchElementException();
return value[pos++];
}
@Override
public void remove() {
if (pos == 0) throw new IllegalStateException();
final int tail = size - pos;
System.arraycopy(key, pos, key, pos - 1, tail);
System.arraycopy(value, pos, value, pos - 1, tail);
size--;
pos--;
Object2DoubleArrayMap.this.key[size] = null;
}
@Override
public void forEachRemaining(final java.util.function.DoubleConsumer action) {
final double[] value = Object2DoubleArrayMap.this.value;
final int max = size;
while (pos < max) {
action.accept(value[pos++]);
}
}
// TODO either override skip or extend from AbstractIndexBasedIterator.
};
}
final class ValuesSpliterator extends it.unimi.dsi.fastutil.doubles.DoubleSpliterators.EarlyBindingSizeIndexBasedSpliterator implements it.unimi.dsi.fastutil.doubles.DoubleSpliterator {
ValuesSpliterator(int pos, int maxPos) {
super(pos, maxPos);
}
@Override
public int characteristics() {
return it.unimi.dsi.fastutil.doubles.DoubleSpliterators.COLLECTION_SPLITERATOR_CHARACTERISTICS | java.util.Spliterator.SUBSIZED | java.util.Spliterator.ORDERED;
}
@Override
protected final double get(int location) {
return value[location];
}
@Override
protected final ValuesSpliterator makeForSplit(int pos, int maxPos) {
return new ValuesSpliterator(pos, maxPos);
}
@Override
public void forEachRemaining(final java.util.function.DoubleConsumer action) {
final double[] value = Object2DoubleArrayMap.this.value;
final int max = size;
while (pos < max) {
action.accept(value[pos++]);
}
}
}
@Override
public it.unimi.dsi.fastutil.doubles.DoubleSpliterator spliterator() {
return new ValuesSpliterator(0, size);
}
@Override
public void forEach(java.util.function.DoubleConsumer action) {
final double[] value = Object2DoubleArrayMap.this.value;
for (int i = 0, max = size; i < max; ++i) {
action.accept(value[i]);
}
}
@Override
public int size() {
return size;
}
@Override
public void clear() {
Object2DoubleArrayMap.this.clear();
}
}
@Override
public DoubleCollection values() {
if (values == null) values = new ValuesCollection();
return values;
}
/**
* Returns a deep copy of this map.
*
*
* This method performs a deep copy of this hash map; the data stored in the map, however, is not
* cloned. Note that this makes a difference only for object keys.
*
* @return a deep copy of this map.
*/
@Override
@SuppressWarnings("unchecked")
public Object2DoubleArrayMap clone() {
Object2DoubleArrayMap c;
try {
c = (Object2DoubleArrayMap)super.clone();
} catch (CloneNotSupportedException cantHappen) {
throw new InternalError();
}
c.key = key.clone();
c.value = value.clone();
c.entries = null;
c.keys = null;
c.values = null;
return c;
}
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
s.defaultWriteObject();
final Object[] key = this.key;
final double[] value = this.value;
for (int i = 0, max = size; i < max; i++) {
s.writeObject(key[i]);
s.writeDouble(value[i]);
}
}
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
final Object[] key = this.key = new Object[size];
final double[] value = this.value = new double[size];
for (int i = 0; i < size; i++) {
key[i] = s.readObject();
value[i] = s.readDouble();
}
}
}