import jalview.datamodel.AlignmentI;
import jalview.datamodel.Annotation;
import jalview.datamodel.SequenceI;
+import jalview.ext.android.SparseIntArray;
+import jalview.util.Comparison;
import jalview.util.Format;
import jalview.util.MappingUtils;
import jalview.util.QuickSort;
public static final void calculate(SequenceI[] sequences, int start,
int end, Hashtable[] result, boolean profile)
{
+ long now = System.currentTimeMillis();
Hashtable residueHash;
- int maxCount, nongap, i, j, v;
- int jSize = sequences.length;
- String maxResidue;
+ int seqCount = sequences.length;
char c = '-';
- float percentage;
+ SparseIntArray profileSizes = new SparseIntArray();
- int[] values = new int[255];
-
- char[] seq;
-
- for (i = start; i < end; i++)
+ for (int column = start; column < end; column++)
{
residueHash = new Hashtable();
- maxCount = 0;
- maxResidue = "";
- nongap = 0;
- values = new int[255];
-
- for (j = 0; j < jSize; j++)
+ int maxCount = 0;
+ String maxResidue = "";
+ int nongap = 0;
+ // int [] values = new int[255];
+ int guessProfileSize = estimateProfileSize(profileSizes);
+ SparseIntArray values = new SparseIntArray(guessProfileSize);
+
+ for (int row = 0; row < seqCount; row++)
{
- if (sequences[j] == null)
+ if (sequences[row] == null)
{
System.err
.println("WARNING: Consensus skipping null sequence - possible race condition.");
continue;
}
- seq = sequences[j].getSequence();
- if (seq.length > i)
+ char[] seq = sequences[row].getSequence();
+ if (seq.length > column)
{
- c = seq[i];
-
- if (c == '.' || c == ' ')
- {
- c = '-';
- }
+ c = seq[column];
- if (c == '-')
+ if (Comparison.isGap(c))
{
- values['-']++;
+ // values['-']++;
+ // values.put('-', values.get('-') + 1);
+ values.add('-', 1);
continue;
}
else if ('a' <= c && c <= 'z')
}
nongap++;
- values[c]++;
-
+ // values[c]++;
+ // values.put(c, values.get(c) + 1);
+ values.add(c, 1);
}
else
{
- values['-']++;
+ /*
+ * here we count as a gap if the sequence doesn't
+ * reach this column (is that correct?)
+ */
+ // values['-']++;
+ // values.put('-', values.get('-') + 1);
+ values.add('-', 1);
}
}
- if (jSize == 1)
+ if (seqCount == 1)
{
maxResidue = String.valueOf(c);
maxCount = 1;
}
else
{
- for (v = 'A'; v <= 'Z'; v++)
+ // iterate over values keys not alphabet
+ // for (int v = 'A'; v <= 'Z'; v++)
+ for (int k = 0; k < values.size(); k++)
{
- // TODO why ignore values[v] == 1?
- if (values[v] < 1 /* 2 */|| values[v] < maxCount)
+ int v = values.keyAt(k);
+ int count = values.valueAt(k); // values[v];
+ if (v == '-' || count < 1 || count < maxCount)
{
continue;
}
- if (values[v] > maxCount)
+ if (count > maxCount)
{
- maxResidue = CHARS[v - 'A'];
+ maxResidue = String.valueOf((char) v);// CHARS[v - 'A'];
}
- else if (values[v] == maxCount)
+ else if (count == maxCount)
{
- maxResidue += CHARS[v - 'A'];
+ maxResidue += String.valueOf((char) v); // CHARS[v - 'A'];
}
- maxCount = values[v];
+ maxCount = count;
}
}
if (maxResidue.length() == 0)
}
if (profile)
{
- // TODO use a 1-dimensional array with jSize, nongap in [0] and [1]
- residueHash.put(PROFILE, new int[][] { values,
- new int[] { jSize, nongap } });
+ // residueHash.put(PROFILE, new int[][] { values,
+ // new int[] { jSize, nongap } });
+ residueHash.put(PROFILE, new Profile(values, seqCount, nongap));
}
residueHash.put(MAXCOUNT, new Integer(maxCount));
residueHash.put(MAXRESIDUE, maxResidue);
- percentage = ((float) maxCount * 100) / jSize;
+ float percentage = ((float) maxCount * 100) / seqCount;
residueHash.put(PID_GAPS, new Float(percentage));
if (nongap > 0)
}
residueHash.put(PID_NOGAPS, new Float(percentage));
- result[i] = residueHash;
+ result[column] = residueHash;
+
+ profileSizes.add(values.size(), 1);
}
+ long elapsed = System.currentTimeMillis() - now;
+ System.out.println(elapsed);
+ }
+
+ /**
+ * Make an estimate of the profile size we are going to compute i.e. how many
+ * different characters may be present in it. Overestimating has a cost of
+ * using more memory than necessary. Underestimating has a cost of needing to
+ * extend the SparseIntArray holding the profile counts.
+ *
+ * @param profileSizes
+ * counts of sizes of profiles so far encountered
+ * @return
+ */
+ static int estimateProfileSize(SparseIntArray profileSizes)
+ {
+ if (profileSizes.size() == 0)
+ {
+ return 4;
+ }
+
+ /*
+ * could do a statistical heuristic here e.g. 75%ile
+ * for now just return the largest value
+ */
+ return profileSizes.keyAt(profileSizes.size() - 1);
}
/**
boolean ignoreGapsInConsensusCalculation,
boolean includeAllConsSymbols, char[] alphabet, long nseq)
{
+ long now = System.currentTimeMillis();
if (consensus == null || consensus.annotations == null
|| consensus.annotations.length < width)
{
{
mouseOver.append(hci.get(AAFrequency.MAXRESIDUE) + " ");
}
- int[][] profile = (int[][]) hci.get(AAFrequency.PROFILE);
+ // int[][] profile = (int[][]) hci.get(AAFrequency.PROFILE);
+ Profile profile = (Profile) hci.get(AAFrequency.PROFILE);
if (profile != null && includeAllConsSymbols)
{
- int sequenceCount = profile[1][0];
- int nonGappedCount = profile[1][1];
+ int sequenceCount = profile.height;// profile[1][0];
+ int nonGappedCount = profile.nonGapped;// [1][1];
int normalisedBy = ignoreGapsInConsensusCalculation ? nonGappedCount
: sequenceCount;
mouseOver.setLength(0);
- if (alphabet != null)
+ // TODO do this sort once only in calculate()?
+ // char[][] ca = new char[profile[0].length][];
+ // /int length = profile[0].length;
+ int length = profile.profile.size();
+ char[] ca = new char[length];
+ // float[] vl = new float[length];
+ int[] vl = new int[length];
+ for (int c = 0; c < ca.length; c++)
+ {
+ int theChar = profile.profile.keyAt(c);
+ ca[c] = (char) theChar;// c;
+ // ca[c] = new char[]
+ // { (char) c };
+ vl[c] = profile.profile.valueAt(c);// profile[0][c];
+ }
+
+ /*
+ * sort characters into ascending order of their counts
+ */
+ QuickSort.sort(vl, ca);
+
+ /*
+ * traverse in reverse order (highest count first) to build tooltip
+ */
+ // for (int p = 0, c = ca.length - 1; profile[0][ca[c]] > 0; c--)
+ for (int p = 0, c = ca.length - 1; c >= 0; c--)
{
- for (int c = 0; c < alphabet.length; c++)
+ final char residue = ca[c];
+ if (residue != '-')
{
- float tval = profile[0][alphabet[c]] * 100f / normalisedBy;
+ // float tval = profile[0][residue] * 100f / normalisedBy;
+ // float tval = profile[0][residue] * 100f / normalisedBy;
+ float tval = (vl[c] * 100f) / normalisedBy;
mouseOver
- .append(((c == 0) ? "" : "; "))
- .append(alphabet[c])
+ .append((((p == 0) ? "" : "; ")))
+ .append(residue)
.append(" ")
.append(((fmt != null) ? fmt.form(tval) : ((int) tval)))
.append("%");
- }
- }
- else
- {
- // TODO do this sort once only in calculate()?
- // char[][] ca = new char[profile[0].length][];
- char[] ca = new char[profile[0].length];
- float[] vl = new float[profile[0].length];
- for (int c = 0; c < ca.length; c++)
- {
- ca[c] = (char) c;
- // ca[c] = new char[]
- // { (char) c };
- vl[c] = profile[0][c];
- }
- QuickSort.sort(vl, ca);
- for (int p = 0, c = ca.length - 1; profile[0][ca[c]] > 0; c--)
- {
- final char residue = ca[c];
- if (residue != '-')
- {
- float tval = profile[0][residue] * 100f / normalisedBy;
- mouseOver
- .append((((p == 0) ? "" : "; ")))
- .append(residue)
- .append(" ")
- .append(((fmt != null) ? fmt.form(tval)
- : ((int) tval))).append("%");
- p++;
- }
+ p++;
}
}
}
consensus.annotations[i] = new Annotation(maxRes,
mouseOver.toString(), ' ', value);
}
+ long elapsed = System.currentTimeMillis() - now;
+ System.out.println(-elapsed);
}
/**
boolean ignoreGaps)
{
int[] rtnval = new int[64];
- int[][] profile = (int[][]) hconsensus.get(AAFrequency.PROFILE);
+ // int[][] profile = (int[][]) hconsensus.get(AAFrequency.PROFILE);
+ Profile profile = (Profile) hconsensus.get(AAFrequency.PROFILE);
if (profile == null)
{
return null;
}
- char[] ca = new char[profile[0].length];
- float[] vl = new float[profile[0].length];
- for (int c = 0; c < ca.length; c++)
+ // int profileLength = profile[0].length;
+ int profileLength = profile.profile.size();
+ char[] ca = new char[profileLength];
+ float[] vl = new float[profileLength];
+ // for (int c = 0; c < ca.length; c++)
+ // {
+ // ca[c] = (char) c;
+ // vl[c] = profile[0][c];
+ // }
+ for (int i = 0; i < profileLength; i++)
{
- ca[c] = (char) c;
- vl[c] = profile[0][c];
+ int c = profile.profile.keyAt(i);
+ ca[i] = (char) c;
+ vl[i] = profile.profile.valueAt(i);
}
QuickSort.sort(vl, ca);
int nextArrayPos = 2;
int totalPercentage = 0;
int distinctValuesCount = 0;
- final int divisor = profile[1][ignoreGaps ? 1 : 0];
- for (int c = ca.length - 1; profile[0][ca[c]] > 0; c--)
+ final int divisor = ignoreGaps ? profile.nonGapped : profile.height;
+ // final int divisor = profile[1][ignoreGaps ? 1 : 0];
+ int j = profile.profile.size();
+ for (int i = 0; i < j; i++)
+// for (int c = ca.length - 1; profile[0][ca[c]] > 0; c--)
{
- if (ca[c] != '-')
+ int theChar = profile.profile.keyAt(i);
+ int charCount = profile.profile.valueAt(i);
+
+// if (ca[c] != '-')
+ if (theChar != '-')
{
- rtnval[nextArrayPos++] = ca[c];
- final int percentage = (int) (profile[0][ca[c]] * 100f / divisor);
+// rtnval[nextArrayPos++] = ca[c];
+ rtnval[nextArrayPos++] = theChar;
+// final int percentage = (int) (profile[0][ca[c]] * 100f / divisor);
+ final int percentage = (charCount * 100) / divisor;
rtnval[nextArrayPos++] = percentage;
totalPercentage += percentage;
distinctValuesCount++;
import jalview.datamodel.Annotation;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceI;
+import jalview.ext.android.SparseIntArray;
import jalview.schemes.ResidueProperties;
import java.awt.Color;
-import java.util.Hashtable;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
import java.util.Vector;
/**
*/
public void calculate()
{
- int thresh, j, jSize = sequences.length;
- int[] values; // Replaces residueHash
- char c;
+ int jSize = sequences.length;
+ // int[] values; // Replaces residueHash
+ SparseIntArray values = new SparseIntArray();
- total = new Hashtable[maxLength];
+ total = new Map[maxLength];
for (int i = start; i <= end; i++)
{
- values = new int[255];
+ // values = new int[255];
+ values.clear();
- for (j = 0; j < jSize; j++)
+ for (int j = 0; j < jSize; j++)
{
if (sequences[j].getLength() > i)
{
- c = sequences[j].getCharAt(i);
+ char c = sequences[j].getCharAt(i);
if (canonicaliseAa)
{ // lookup the base aa code symbol
c = toUpperCase(c);
}
- values[c]++;
+ // values[c]++;
+ values.add(c, 1);
}
else
{
- values['-']++;
+ // values['-']++;
+ values.add('-', 1);
}
}
// What is the count threshold to count the residues in residueHash()
- thresh = (threshold * (jSize)) / 100;
+ int thresh = (threshold * jSize) / 100;
// loop over all the found residues
- Hashtable<String, Integer> resultHash = new Hashtable<String, Integer>();
- for (char v = '-'; v < 'Z'; v++)
+ // Hashtable<String, Integer> resultHash = new Hashtable<String,
+ // Integer>();
+ Map<String, Integer> resultHash = new TreeMap<String, Integer>();
+ // for (char v = '-'; v < 'Z'; v++)
+ for (int key = 0; key < values.size(); key++)
{
-
- if (values[v] > thresh)
+ char v = (char) values.keyAt(key);
+ // if (values[v] > thresh)
+ if (values.valueAt(key) > thresh)
{
String res = String.valueOf(v);
*/
public void verdict(boolean consflag, float percentageGaps)
{
- StringBuffer consString = new StringBuffer();
+ StringBuilder consString = new StringBuilder(end);
// NOTE THIS SHOULD CHECK IF THE CONSEQUENCE ALREADY
// EXISTS AND NOT OVERWRITE WITH '-', BUT THIS CASE
int[] gapcons = countConsNGaps(i);
int totGaps = gapcons[1];
float pgaps = ((float) totGaps * 100) / sequences.length;
- consSymbs[i - start] = new String();
+ StringBuilder positives = new StringBuilder(64);
+ StringBuilder negatives = new StringBuilder(32);
+ // consSymbs[i - start] = "";
if (percentageGaps > pgaps)
{
{
if (result == 1)
{
- consSymbs[i - start] = type + " " + consSymbs[i - start];
+ // consSymbs[i - start] = type + " " + consSymbs[i - start];
+ positives.append(positives.length() == 0 ? "" : " ");
+ positives.append(type);
count++;
}
}
{
if (result == 0)
{
- consSymbs[i - start] = consSymbs[i - start] + " !" + type;
+ /*
+ * add negatively conserved properties on the end
+ */
+ // consSymbs[i - start] = consSymbs[i - start] + " !" + type;
+ negatives.append(negatives.length() == 0 ? "" : " ");
+ negatives.append("!").append(type);
}
else
{
- consSymbs[i - start] = type + " " + consSymbs[i - start];
+ /*
+ * put positively conserved properties on the front
+ */
+ // consSymbs[i - start] = type + " " + consSymbs[i - start];
+ positives.append(positives.length() == 0 ? "" : " ");
+ positives.append(type);
}
count++;
}
}
}
+ if (negatives.length() > 0)
+ {
+ positives.append(" ").append(negatives);
+ }
+ consSymbs[i - start] = positives.toString();
if (count < 10)
{
--- /dev/null
+package jalview.analysis;
+
+import jalview.ext.android.SparseIntArray;
+
+public class Profile
+{
+ /*
+ * array of keys (chars) and values (counts)
+ */
+ public final SparseIntArray profile;
+
+ /*
+ * the number of sequences in the profile
+ */
+ public final int height;
+
+ /*
+ * the number of non-gapped sequences in the profile
+ */
+ public final int nonGapped;
+
+ public Profile(SparseIntArray counts, int ht, int nongappedCount)
+ {
+ this.profile = counts;
+ this.height = ht;
+ this.nonGapped = nongappedCount;
+ }
+
+ public SparseIntArray getProfile()
+ {
+ return profile;
+ }
+}
--- /dev/null
+package jalview.ext.android;
+
+/*
+ * Copyright (C) 2013 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.
+ */
+
+class ContainerHelpers
+{
+ static final boolean[] EMPTY_BOOLEANS = new boolean[0];
+
+ static final int[] EMPTY_INTS = new int[0];
+
+ static final long[] EMPTY_LONGS = new long[0];
+
+ static final Object[] EMPTY_OBJECTS = new Object[0];
+
+ // This is Arrays.binarySearch(), but doesn't do any argument validation.
+ static int binarySearch(int[] array, int size, int value)
+ {
+ int lo = 0;
+ int hi = size - 1;
+ while (lo <= hi)
+ {
+ final int mid = (lo + hi) >>> 1;
+ final int midVal = array[mid];
+ if (midVal < value)
+ {
+ lo = mid + 1;
+ }
+ else if (midVal > value)
+ {
+ hi = mid - 1;
+ }
+ else
+ {
+ return mid; // value found
+ }
+ }
+ return ~lo; // value not present
+ }
+
+ static int binarySearch(long[] array, int size, long value)
+ {
+ int lo = 0;
+ int hi = size - 1;
+ while (lo <= hi)
+ {
+ final int mid = (lo + hi) >>> 1;
+ final long midVal = array[mid];
+ if (midVal < value)
+ {
+ lo = mid + 1;
+ }
+ else if (midVal > value)
+ {
+ hi = mid - 1;
+ }
+ else
+ {
+ return mid; // value found
+ }
+ }
+ return ~lo; // value not present
+ }
+}
--- /dev/null
+package jalview.ext.android;
+
+/*
+ * Copyright (C) 2006 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.
+ */
+/**
+ * SparseIntArrays map integers to integers. Unlike a normal array of integers,
+ * there can be gaps in the indices. It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Integers, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra
+ * entry object for each mapping.
+ *
+ * <p>
+ * Note that this container keeps its mappings in an array data structure, using
+ * a binary search to find keys. The implementation is not intended to be
+ * appropriate for data structures that may contain large numbers of items. It
+ * is generally slower than a traditional HashMap, since lookups require a
+ * binary search and adds and removes require inserting and deleting entries in
+ * the array. For containers holding up to hundreds of items, the performance
+ * difference is not significant, less than 50%.
+ * </p>
+ *
+ * <p>
+ * It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)<code>.
+ * </p>
+ */
+public class SparseIntArray implements Cloneable
+{
+ private int[] mKeys;
+
+ private int[] mValues;
+
+ private int mSize;
+
+ /**
+ * Creates a new SparseIntArray containing no mappings.
+ */
+ public SparseIntArray()
+ {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseIntArray containing no mappings that will not require
+ * any additional memory allocation to store the specified number of mappings.
+ * If you supply an initial capacity of 0, the sparse array will be
+ * initialized with a light-weight representation not requiring any additional
+ * array allocations.
+ */
+ public SparseIntArray(int initialCapacity)
+ {
+ if (initialCapacity == 0)
+ {
+ mKeys = ContainerHelpers.EMPTY_INTS;
+ mValues = ContainerHelpers.EMPTY_INTS;
+ }
+ else
+ {
+ initialCapacity = idealIntArraySize(initialCapacity);
+ mKeys = new int[initialCapacity];
+ mValues = new int[initialCapacity];
+ }
+ mSize = 0;
+ }
+
+ @Override
+ public SparseIntArray clone()
+ {
+ SparseIntArray clone = null;
+ try
+ {
+ clone = (SparseIntArray) super.clone();
+ clone.mKeys = mKeys.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse)
+ {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the int mapped from the specified key, or <code>0</code> if no such
+ * mapping has been made.
+ */
+ public int get(int key)
+ {
+ return get(key, 0);
+ }
+
+ /**
+ * Gets the int mapped from the specified key, or the specified value if no
+ * such mapping has been made.
+ */
+ public int get(int key, int valueIfKeyNotFound)
+ {
+ int i = ContainerHelpers.binarySearch(mKeys, 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(int key)
+ {
+ int i = ContainerHelpers.binarySearch(mKeys, 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(int key, int value)
+ {
+ int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+ if (i >= 0)
+ {
+ mValues[i] = value;
+ }
+ else
+ {
+ i = ~i;
+ if (mSize >= mKeys.length)
+ {
+ int n = idealIntArraySize(mSize + 1);
+ int[] nkeys = new int[n];
+ int[] nvalues = new int[n];
+ // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+ if (mSize - i != 0)
+ {
+ // Log.e("SparseIntArray", "move " + (mSize - i));
+ 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 SparseIntArray stores.
+ *
+ * <p>
+ * The keys corresponding to indices in ascending order are guaranteed to be
+ * in ascending order, e.g., <code>keyAt(0)</code> will return the smallest
+ * key and <code>keyAt(size()-1)</code> will return the largest key.
+ * </p>
+ */
+ public int 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 SparseIntArray
+ * stores.
+ *
+ * <p>
+ * The values corresponding to indices in ascending order are guaranteed to be
+ * associated with keys in ascending order, e.g., <code>valueAt(0)</code> will
+ * return the value associated with the smallest key and
+ * <code>valueAt(size()-1)</code> will return the value associated with the
+ * largest key.
+ * </p>
+ */
+ public int 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(int key)
+ {
+ return ContainerHelpers.binarySearch(mKeys, 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(int 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(int key, int value)
+ {
+ if (mSize != 0 && key <= mKeys[mSize - 1])
+ {
+ put(key, value);
+ return;
+ }
+ int pos = mSize;
+ if (pos >= mKeys.length)
+ {
+ int n = idealIntArraySize(pos + 1);
+ int[] nkeys = new int[n];
+ int[] nvalues = new int[n];
+ // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+ mKeys[pos] = key;
+ mValues[pos] = value;
+ mSize = pos + 1;
+ }
+
+ /**
+ * Inlined here by copying from com.android.internal.util.ArrayUtils
+ *
+ * @param i
+ * @return
+ */
+ public static int idealIntArraySize(int need)
+ {
+ return idealByteArraySize(need * 4) / 4;
+ }
+
+ /**
+ * Inlined here by copying from com.android.internal.util.ArrayUtils
+ *
+ * @param i
+ * @return
+ */
+ public static int idealByteArraySize(int need)
+ {
+ for (int i = 4; i < 32; i++)
+ {
+ if (need <= (1 << i) - 12)
+ {
+ return (1 << i) - 12;
+ }
+ }
+
+ return need;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * This implementation composes a string by iterating over its mappings.
+ */
+ @Override
+ public String toString()
+ {
+ if (size() <= 0)
+ {
+ return "{}";
+ }
+ StringBuilder buffer = new StringBuilder(mSize * 28);
+ buffer.append('{');
+ for (int i = 0; i < mSize; i++)
+ {
+ if (i > 0)
+ {
+ buffer.append(", ");
+ }
+ int key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ int value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+
+ /**
+ * Method (copied from put) added for Jalview to efficiently increment a key's
+ * value if present, else add it with the given value. This avoids a double
+ * binary search (once to get the value, again to put the updated value).
+ *
+ * @param key
+ * @oparam toAdd
+ */
+ public void add(int key, int toAdd)
+ {
+ int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+ if (i >= 0)
+ {
+ mValues[i] += toAdd;
+ }
+ else
+ {
+ i = ~i;
+ if (mSize >= mKeys.length)
+ {
+ int n = idealIntArraySize(mSize + 1);
+ int[] nkeys = new int[n];
+ int[] nvalues = new int[n];
+ // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+ if (mSize - i != 0)
+ {
+ // Log.e("SparseIntArray", "move " + (mSize - i));
+ System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+ System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+ }
+ mKeys[i] = key;
+ mValues[i] = toAdd;
+ mSize++;
+ }
+ }
+}