Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e95724ae authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Add LongSparseLongArray with tests."

parents 49464796 dda73b5d
Loading
Loading
Loading
Loading
+228 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+111 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}