diff options
| author | 2013-01-18 16:56:49 -0800 | |
|---|---|---|
| committer | 2013-01-18 16:59:14 -0800 | |
| commit | dda73b5dcd92006762a1c71e2fb352e64fa265ef (patch) | |
| tree | 747dc2eca6e82cf29cd8f75cc82abe2da4573314 | |
| parent | 66a017b63461a22842b3678c9520f803d5ddadfc (diff) | |
Add LongSparseLongArray with tests.
Change-Id: Iae32ba7647601c587e30834379d7d3c2235c75b0
| -rw-r--r-- | core/java/android/util/LongSparseLongArray.java | 228 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/util/LongSparseLongArrayTest.java | 111 |
2 files changed, 339 insertions, 0 deletions
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java new file mode 100644 index 000000000000..34b61264d653 --- /dev/null +++ b/core/java/android/util/LongSparseLongArray.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.util; + +import com.android.internal.util.ArrayUtils; + +import java.util.Arrays; + +/** + * Map of {@code long} to {@code long}. Unlike a normal array of longs, there + * can be gaps in the indices. It is intended to be more efficient than using a + * {@code HashMap}. + * + * @hide + */ +public class LongSparseLongArray implements Cloneable { + private long[] mKeys; + private long[] mValues; + private int mSize; + + /** + * Creates a new SparseLongArray containing no mappings. + */ + public LongSparseLongArray() { + this(10); + } + + /** + * Creates a new SparseLongArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. + */ + public LongSparseLongArray(int initialCapacity) { + initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); + + mKeys = new long[initialCapacity]; + mValues = new long[initialCapacity]; + mSize = 0; + } + + @Override + public LongSparseLongArray clone() { + LongSparseLongArray clone = null; + try { + clone = (LongSparseLongArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException cnse) { + /* ignore */ + } + return clone; + } + + /** + * Gets the long mapped from the specified key, or <code>0</code> + * if no such mapping has been made. + */ + public long get(long key) { + return get(key, 0); + } + + /** + * Gets the long mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public long get(long key, long valueIfKeyNotFound) { + int i = Arrays.binarySearch(mKeys, 0, mSize, key); + + if (i < 0) { + return valueIfKeyNotFound; + } else { + return mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(long key) { + int i = Arrays.binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + removeAt(i); + } + } + + /** + * Removes the mapping at the given index. + */ + public void removeAt(int index) { + System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); + System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1)); + mSize--; + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(long key, long value) { + int i = Arrays.binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + + if (mSize >= mKeys.length) { + growKeyAndValueArrays(mSize + 1); + } + + if (mSize - i != 0) { + System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); + System.arraycopy(mValues, i, mValues, i + 1, mSize - i); + } + + mKeys[i] = key; + mValues[i] = value; + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseIntArray + * currently stores. + */ + public int size() { + return mSize; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseLongArray stores. + */ + public long keyAt(int index) { + return mKeys[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseLongArray stores. + */ + public long valueAt(int index) { + return mValues[index]; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(long key) { + return Arrays.binarySearch(mKeys, 0, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(long value) { + for (int i = 0; i < mSize; i++) + if (mValues[i] == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseIntArray. + */ + public void clear() { + mSize = 0; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(long key, long value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + int pos = mSize; + if (pos >= mKeys.length) { + growKeyAndValueArrays(pos + 1); + } + + mKeys[pos] = key; + mValues[pos] = value; + mSize = pos + 1; + } + + private void growKeyAndValueArrays(int minNeededSize) { + int n = ArrayUtils.idealLongArraySize(minNeededSize); + + long[] nkeys = new long[n]; + long[] nvalues = new long[n]; + + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } +} diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java new file mode 100644 index 000000000000..cb468bc68eeb --- /dev/null +++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.util; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; + +/** + * Tests for {@link LongSparseLongArray}. + */ +public class LongSparseLongArrayTest extends TestCase { + private static final String TAG = "LongSparseLongArrayTest"; + + public void testSimplePut() throws Exception { + final LongSparseLongArray array = new LongSparseLongArray(5); + for (int i = 0; i < 48; i++) { + final long value = 1 << i; + array.put(value, value); + } + for (int i = 0; i < 48; i++) { + final long value = 1 << i; + assertEquals(value, array.get(value, -1)); + assertEquals(-1, array.get(-value, -1)); + } + } + + public void testSimplePutBackwards() throws Exception { + final LongSparseLongArray array = new LongSparseLongArray(5); + for (int i = 47; i >= 0; i--) { + final long value = 1 << i; + array.put(value, value); + } + for (int i = 0; i < 48; i++) { + final long value = 1 << i; + assertEquals(value, array.get(value, -1)); + assertEquals(-1, array.get(-value, -1)); + } + } + + public void testMiddleInsert() throws Exception { + final LongSparseLongArray array = new LongSparseLongArray(5); + for (int i = 0; i < 48; i++) { + final long value = 1 << i; + array.put(value, value); + } + final long special = (1 << 24) + 5; + array.put(special, 1024); + for (int i = 0; i < 48; i++) { + final long value = 1 << i; + assertEquals(value, array.get(value, -1)); + assertEquals(-1, array.get(-value, -1)); + } + assertEquals(1024, array.get(special, -1)); + } + + public void testFuzz() throws Exception { + final Random r = new Random(); + + final HashMap<Long, Long> map = new HashMap<Long, Long>(); + final LongSparseLongArray array = new LongSparseLongArray(r.nextInt(128)); + + for (int i = 0; i < 10240; i++) { + if (r.nextBoolean()) { + final long key = r.nextLong(); + final long value = r.nextLong(); + map.put(key, value); + array.put(key, value); + } + if (r.nextBoolean() && map.size() > 0) { + final int index = r.nextInt(map.size()); + final long key = getKeyAtIndex(map, index); + map.remove(key); + array.delete(key); + } + } + + Log.d(TAG, "verifying a map with " + map.size() + " entries"); + + for (Map.Entry<Long, Long> e : map.entrySet()) { + final long key = e.getKey(); + final long value = e.getValue(); + assertEquals(value, array.get(key)); + } + } + + private static <E> E getKeyAtIndex(Map<E, ?> map, int index) { + final Iterator<E> keys = map.keySet().iterator(); + for (int i = 0; i < index; i++) { + keys.next(); + } + return keys.next(); + } +} |