Merge "A space-efficient 2D matrix" into sc-dev
This commit is contained in:
@@ -16,9 +16,12 @@
|
|||||||
|
|
||||||
package com.android.server.utils;
|
package com.android.server.utils;
|
||||||
|
|
||||||
|
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.Size;
|
import android.annotation.Size;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
import com.android.internal.util.GrowingArrayUtils;
|
import com.android.internal.util.GrowingArrayUtils;
|
||||||
|
|
||||||
@@ -39,13 +42,14 @@ import java.util.Arrays;
|
|||||||
public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappable {
|
public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The matrix is implemented through four arrays. The matrix of booleans is stored in
|
* The matrix is implemented through four arrays. First, the matrix of booleans is
|
||||||
* a one-dimensional {@code mValues} array. {@code mValues} is always of size
|
* stored in a two-dimensional {@code mValues} array of bit-packed booleans.
|
||||||
* {@code mOrder * mOrder}. Elements of {@code mValues} are addressed with
|
* {@code mValues} is always of size {@code mOrder * mOrder / 8}. The factor of 8 is
|
||||||
* arithmetic: the offset of the element {@code {row, col}} is at
|
* present because there are 8 bits in a byte. Elements of {@code mValues} are
|
||||||
* {@code row * mOrder + col}. The term "storage index" applies to {@code mValues}.
|
* addressed with arithmetic: the element {@code {row, col}} is bit {@code col % 8} in
|
||||||
* A storage index designates a row (column) in the underlying storage. This is not
|
* byte * {@code (row * mOrder + col) / 8}. The term "storage index" applies to
|
||||||
* the same as the row seen by client code.
|
* {@code mValues}. A storage index designates a row (column) in the underlying
|
||||||
|
* storage. This is not the same as the row seen by client code.
|
||||||
*
|
*
|
||||||
* Client code addresses the matrix through indices. These are integers that need not
|
* Client code addresses the matrix through indices. These are integers that need not
|
||||||
* be contiguous. Client indices are mapped to storage indices through two linear
|
* be contiguous. Client indices are mapped to storage indices through two linear
|
||||||
@@ -61,16 +65,32 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
*
|
*
|
||||||
* Some notes:
|
* Some notes:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li> The matrix never shrinks.
|
* <li> The matrix does not automatically shrink but there is a compress() method that
|
||||||
|
* will recover unused space.
|
||||||
* <li> Equality is a very, very expesive operation.
|
* <li> Equality is a very, very expesive operation.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mOrder is always a multiple of this value. A minimal matrix therefore holds 2^12
|
* mOrder is always a multiple of this value. A minimal matrix therefore holds 2^12
|
||||||
* values and requires 1024 bytes.
|
* values and requires 1024 bytes. The value is visible for testing.
|
||||||
*/
|
*/
|
||||||
private static final int STEP = 64;
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
|
static final int STEP = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are 8 bits in a byte. The constant is defined here only to make it easy to
|
||||||
|
* find in the code.
|
||||||
|
*/
|
||||||
|
private static final int BYTE = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants that index into the string array returned by matrixToString. The primary
|
||||||
|
* consumer is test code.
|
||||||
|
*/
|
||||||
|
static final int STRING_KEY_INDEX = 0;
|
||||||
|
static final int STRING_MAP_INDEX = 1;
|
||||||
|
static final int STRING_INUSE_INDEX = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The order of the matrix storage, including any padding. The matrix is always
|
* The order of the matrix storage, including any padding. The matrix is always
|
||||||
@@ -103,7 +123,7 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
/**
|
/**
|
||||||
* The boolean array. This array is always {@code mOrder x mOrder} in size.
|
* The boolean array. This array is always {@code mOrder x mOrder} in size.
|
||||||
*/
|
*/
|
||||||
private boolean[] mValues;
|
private byte[] mValues;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convenience function called when the elements are added to or removed from the storage.
|
* A convenience function called when the elements are added to or removed from the storage.
|
||||||
@@ -140,7 +160,7 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
mInUse = new boolean[mOrder];
|
mInUse = new boolean[mOrder];
|
||||||
mKeys = ArrayUtils.newUnpaddedIntArray(mOrder);
|
mKeys = ArrayUtils.newUnpaddedIntArray(mOrder);
|
||||||
mMap = ArrayUtils.newUnpaddedIntArray(mOrder);
|
mMap = ArrayUtils.newUnpaddedIntArray(mOrder);
|
||||||
mValues = new boolean[mOrder * mOrder];
|
mValues = new byte[mOrder * mOrder / 8];
|
||||||
mSize = 0;
|
mSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +227,7 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
}
|
}
|
||||||
if (r >= 0 && c >= 0) {
|
if (r >= 0 && c >= 0) {
|
||||||
setValueAt(r, c, value);
|
setValueAt(r, c, value);
|
||||||
onChanged();
|
// setValueAt() will call onChanged().
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("matrix overflow");
|
throw new RuntimeException("matrix overflow");
|
||||||
}
|
}
|
||||||
@@ -232,8 +252,12 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
public void removeAt(int index) {
|
public void removeAt(int index) {
|
||||||
validateIndex(index);
|
validateIndex(index);
|
||||||
mInUse[mMap[index]] = false;
|
mInUse[mMap[index]] = false;
|
||||||
|
// Remove the specified index and ensure that unused words in mKeys and mMap are
|
||||||
|
// always zero, to simplify the equality function.
|
||||||
System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
|
System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
|
||||||
|
mKeys[mSize - 1] = 0;
|
||||||
System.arraycopy(mMap, index + 1, mMap, index, mSize - (index + 1));
|
System.arraycopy(mMap, index + 1, mMap, index, mSize - (index + 1));
|
||||||
|
mMap[mSize - 1] = 0;
|
||||||
mSize--;
|
mSize--;
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
@@ -271,6 +295,17 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
return mKeys[index];
|
return mKeys[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An internal method to fetch the boolean value given the mValues row and column
|
||||||
|
* indices. These are not the indices used by the *At() methods.
|
||||||
|
*/
|
||||||
|
private boolean valueAtInternal(int row, int col) {
|
||||||
|
int element = row * mOrder + col;
|
||||||
|
int offset = element / BYTE;
|
||||||
|
int mask = 1 << (element % BYTE);
|
||||||
|
return (mValues[offset] & mask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a row and column, each in the range <code>0...size()-1</code>, returns the
|
* Given a row and column, each in the range <code>0...size()-1</code>, returns the
|
||||||
* value from the <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix
|
* value from the <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix
|
||||||
@@ -280,8 +315,22 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
validateIndex(rowIndex, colIndex);
|
validateIndex(rowIndex, colIndex);
|
||||||
int r = mMap[rowIndex];
|
int r = mMap[rowIndex];
|
||||||
int c = mMap[colIndex];
|
int c = mMap[colIndex];
|
||||||
int element = r * mOrder + c;
|
return valueAtInternal(r, c);
|
||||||
return mValues[element];
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An internal method to set the boolean value given the mValues row and column
|
||||||
|
* indices. These are not the indices used by the *At() methods.
|
||||||
|
*/
|
||||||
|
private void setValueAtInternal(int row, int col, boolean value) {
|
||||||
|
int element = row * mOrder + col;
|
||||||
|
int offset = element / BYTE;
|
||||||
|
byte mask = (byte) (1 << (element % BYTE));
|
||||||
|
if (value) {
|
||||||
|
mValues[offset] |= mask;
|
||||||
|
} else {
|
||||||
|
mValues[offset] &= ~mask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -291,8 +340,7 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
validateIndex(rowIndex, colIndex);
|
validateIndex(rowIndex, colIndex);
|
||||||
int r = mMap[rowIndex];
|
int r = mMap[rowIndex];
|
||||||
int c = mMap[colIndex];
|
int c = mMap[colIndex];
|
||||||
int element = r * mOrder + c;
|
setValueAtInternal(r, c, value);
|
||||||
mValues[element] = value;
|
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,12 +375,17 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
|
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
|
||||||
mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex);
|
mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex);
|
||||||
mSize++;
|
mSize++;
|
||||||
|
|
||||||
// Initialize the row and column corresponding to the new index.
|
// Initialize the row and column corresponding to the new index.
|
||||||
|
int valueRow = mOrder / BYTE;
|
||||||
|
int offset = newIndex / BYTE;
|
||||||
|
byte mask = (byte) (~(1 << (newIndex % BYTE)));
|
||||||
|
Arrays.fill(mValues, newIndex * valueRow, (newIndex + 1) * valueRow, (byte) 0);
|
||||||
for (int n = 0; n < mSize; n++) {
|
for (int n = 0; n < mSize; n++) {
|
||||||
mValues[n * mOrder + newIndex] = false;
|
mValues[n * valueRow + offset] &= mask;
|
||||||
mValues[newIndex * mOrder + n] = false;
|
|
||||||
}
|
}
|
||||||
onChanged();
|
// Do not report onChanged() from this private method. onChanged() is the
|
||||||
|
// responsibility of public methods that call this one.
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -355,6 +408,33 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
validateIndex(col);
|
validateIndex(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand the 2D array. This also extends the free list.
|
||||||
|
*/
|
||||||
|
private void growMatrix() {
|
||||||
|
resizeValues(mOrder + STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize the values array to the new dimension.
|
||||||
|
*/
|
||||||
|
private void resizeValues(int newOrder) {
|
||||||
|
|
||||||
|
boolean[] newInuse = Arrays.copyOf(mInUse, newOrder);
|
||||||
|
int minOrder = Math.min(mOrder, newOrder);
|
||||||
|
|
||||||
|
byte[] newValues = new byte[newOrder * newOrder / BYTE];
|
||||||
|
for (int i = 0; i < minOrder; i++) {
|
||||||
|
int row = mOrder * i / BYTE;
|
||||||
|
int newRow = newOrder * i / BYTE;
|
||||||
|
System.arraycopy(mValues, row, newValues, newRow, minOrder / BYTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
mInUse = newInuse;
|
||||||
|
mValues = newValues;
|
||||||
|
mOrder = newOrder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an unused storage index, mark it in-use, and return it.
|
* Find an unused storage index, mark it in-use, and return it.
|
||||||
*/
|
*/
|
||||||
@@ -369,27 +449,82 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand the 2D array. This also extends the free list.
|
* Return the index of the key that uses the highest row index in use. This returns
|
||||||
|
* -1 if the matrix is empty. Note that the return is an index suitable for the *At()
|
||||||
|
* methods. It is not the index in the mInUse array.
|
||||||
*/
|
*/
|
||||||
private void growMatrix() {
|
private int lastInuse() {
|
||||||
int newOrder = mOrder + STEP;
|
for (int i = mOrder - 1; i >= 0; i--) {
|
||||||
|
if (mInUse[i]) {
|
||||||
boolean[] newInuse = Arrays.copyOf(mInUse, newOrder);
|
for (int j = 0; j < mSize; j++) {
|
||||||
|
if (mMap[j] == i) {
|
||||||
boolean[] newValues = new boolean[newOrder * newOrder];
|
return j;
|
||||||
for (int i = 0; i < mOrder; i++) {
|
}
|
||||||
int row = mOrder * i;
|
}
|
||||||
int newRow = newOrder * i;
|
throw new IndexOutOfBoundsException();
|
||||||
for (int j = 0; j < mOrder; j++) {
|
|
||||||
int index = row + j;
|
|
||||||
int newIndex = newRow + j;
|
|
||||||
newValues[newIndex] = mValues[index];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
mInUse = newInuse;
|
/**
|
||||||
mValues = newValues;
|
* Compress the matrix by packing keys into consecutive indices. If the compression
|
||||||
mOrder = newOrder;
|
* is sufficient, the mValues array can be shrunk.
|
||||||
|
*/
|
||||||
|
private void pack() {
|
||||||
|
if (mSize == 0 || mSize == mOrder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// dst and src are identify raw (row, col) in mValues. srcIndex is the index (as
|
||||||
|
// in the result of keyAt()) of the key being relocated.
|
||||||
|
for (int dst = nextFree(); dst < mSize; dst = nextFree()) {
|
||||||
|
int srcIndex = lastInuse();
|
||||||
|
int src = mMap[srcIndex];
|
||||||
|
mInUse[src] = false;
|
||||||
|
mMap[srcIndex] = dst;
|
||||||
|
System.arraycopy(mValues, src * mOrder / BYTE,
|
||||||
|
mValues, dst * mOrder / BYTE,
|
||||||
|
mOrder / BYTE);
|
||||||
|
int srcOffset = (src / BYTE);
|
||||||
|
byte srcMask = (byte) (1 << (src % BYTE));
|
||||||
|
int dstOffset = (dst / BYTE);
|
||||||
|
byte dstMask = (byte) (1 << (dst % BYTE));
|
||||||
|
for (int i = 0; i < mOrder; i++) {
|
||||||
|
if ((mValues[srcOffset] & srcMask) == 0) {
|
||||||
|
mValues[dstOffset] &= ~dstMask;
|
||||||
|
} else {
|
||||||
|
mValues[dstOffset] |= dstMask;
|
||||||
|
}
|
||||||
|
srcOffset += mOrder / BYTE;
|
||||||
|
dstOffset += mOrder / BYTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink the matrix, if possible.
|
||||||
|
*/
|
||||||
|
public void compact() {
|
||||||
|
pack();
|
||||||
|
int unused = (mOrder - mSize) / STEP;
|
||||||
|
if (unused > 0) {
|
||||||
|
resizeValues(mOrder - (unused * STEP));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a copy of the keys that are in use by the matrix.
|
||||||
|
*/
|
||||||
|
public int[] keys() {
|
||||||
|
return Arrays.copyOf(mKeys, mSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the size of the 2D matrix. This is always greater than or equal to size().
|
||||||
|
* This does not reflect the sizes of the meta-information arrays (such as mKeys).
|
||||||
|
*/
|
||||||
|
public int capacity() {
|
||||||
|
return mOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -398,15 +533,12 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hashCode = mSize;
|
int hashCode = mSize;
|
||||||
|
hashCode = 31 * hashCode + Arrays.hashCode(mKeys);
|
||||||
|
hashCode = 31 * hashCode + Arrays.hashCode(mMap);
|
||||||
for (int i = 0; i < mSize; i++) {
|
for (int i = 0; i < mSize; i++) {
|
||||||
hashCode = 31 * hashCode + mKeys[i];
|
int row = mMap[i];
|
||||||
hashCode = 31 * hashCode + mMap[i];
|
|
||||||
}
|
|
||||||
for (int i = 0; i < mSize; i++) {
|
|
||||||
int row = mMap[i] * mOrder;
|
|
||||||
for (int j = 0; j < mSize; j++) {
|
for (int j = 0; j < mSize; j++) {
|
||||||
int element = mMap[j] + row;
|
hashCode = 31 * hashCode + (valueAtInternal(row, mMap[j]) ? 1 : 0);
|
||||||
hashCode = 31 * hashCode + (mValues[element] ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hashCode;
|
return hashCode;
|
||||||
@@ -429,20 +561,16 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
if (mSize != other.mSize) {
|
if (mSize != other.mSize) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!Arrays.equals(mKeys, other.mKeys)) {
|
||||||
for (int i = 0; i < mSize; i++) {
|
// mKeys is zero padded at the end and is sorted, so the arrays can always be
|
||||||
if (mKeys[i] != other.mKeys[i]) {
|
// directly compared.
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (mMap[i] != other.mMap[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (int i = 0; i < mSize; i++) {
|
for (int i = 0; i < mSize; i++) {
|
||||||
int row = mMap[i] * mOrder;
|
int row = mMap[i];
|
||||||
for (int j = 0; j < mSize; j++) {
|
for (int j = 0; j < mSize; j++) {
|
||||||
int element = mMap[j] + row;
|
int col = mMap[j];
|
||||||
if (mValues[element] != other.mValues[element]) {
|
if (valueAtInternal(row, col) != other.valueAtInternal(row, col)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,9 +579,12 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the matrix meta information. This is always three strings long.
|
* Return the matrix meta information. This is always three strings long. The
|
||||||
|
* strings are indexed by the constants STRING_KEY_INDEX, STRING_MAP_INDEX, and
|
||||||
|
* STRING_INUSE_INDEX.
|
||||||
*/
|
*/
|
||||||
private @Size(3) String[] matrixToStringMeta() {
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
|
@Size(3) String[] matrixToStringMeta() {
|
||||||
String[] result = new String[3];
|
String[] result = new String[3];
|
||||||
|
|
||||||
StringBuilder k = new StringBuilder();
|
StringBuilder k = new StringBuilder();
|
||||||
@@ -463,7 +594,7 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
k.append(" ");
|
k.append(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result[0] = k.substring(0);
|
result[STRING_KEY_INDEX] = k.substring(0);
|
||||||
|
|
||||||
StringBuilder m = new StringBuilder();
|
StringBuilder m = new StringBuilder();
|
||||||
for (int i = 0; i < mSize; i++) {
|
for (int i = 0; i < mSize; i++) {
|
||||||
@@ -472,42 +603,47 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
|
|||||||
m.append(" ");
|
m.append(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result[1] = m.substring(0);
|
result[STRING_MAP_INDEX] = m.substring(0);
|
||||||
|
|
||||||
StringBuilder u = new StringBuilder();
|
StringBuilder u = new StringBuilder();
|
||||||
for (int i = 0; i < mOrder; i++) {
|
for (int i = 0; i < mOrder; i++) {
|
||||||
u.append(mInUse[i] ? "1" : "0");
|
u.append(mInUse[i] ? "1" : "0");
|
||||||
}
|
}
|
||||||
result[2] = u.substring(0);
|
result[STRING_INUSE_INDEX] = u.substring(0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the matrix as an array of strings. There is one string per row. Each
|
* Return the matrix as an array of strings. There is one string per row. Each
|
||||||
* string has a '1' or a '0' in the proper column.
|
* string has a '1' or a '0' in the proper column. This is the raw data indexed by
|
||||||
|
* row/column disregarding the key map.
|
||||||
*/
|
*/
|
||||||
private String[] matrixToStringRaw() {
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
|
String[] matrixToStringRaw() {
|
||||||
String[] result = new String[mOrder];
|
String[] result = new String[mOrder];
|
||||||
for (int i = 0; i < mOrder; i++) {
|
for (int i = 0; i < mOrder; i++) {
|
||||||
int row = i * mOrder;
|
|
||||||
StringBuilder line = new StringBuilder(mOrder);
|
StringBuilder line = new StringBuilder(mOrder);
|
||||||
for (int j = 0; j < mOrder; j++) {
|
for (int j = 0; j < mOrder; j++) {
|
||||||
int element = row + j;
|
line.append(valueAtInternal(i, j) ? "1" : "0");
|
||||||
line.append(mValues[element] ? "1" : "0");
|
|
||||||
}
|
}
|
||||||
result[i] = line.substring(0);
|
result[i] = line.substring(0);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] matrixToStringCooked() {
|
/**
|
||||||
|
* Return the matrix as an array of strings. There is one string per row. Each
|
||||||
|
* string has a '1' or a '0' in the proper column. This is the cooked data indexed by
|
||||||
|
* keys, in key order.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
|
String[] matrixToStringCooked() {
|
||||||
String[] result = new String[mSize];
|
String[] result = new String[mSize];
|
||||||
for (int i = 0; i < mSize; i++) {
|
for (int i = 0; i < mSize; i++) {
|
||||||
int row = mMap[i] * mOrder;
|
int row = mMap[i];
|
||||||
StringBuilder line = new StringBuilder(mSize);
|
StringBuilder line = new StringBuilder(mSize);
|
||||||
for (int j = 0; j < mSize; j++) {
|
for (int j = 0; j < mSize; j++) {
|
||||||
int element = row + mMap[j];
|
line.append(valueAtInternal(row, mMap[j]) ? "1" : "0");
|
||||||
line.append(mValues[element] ? "1" : "0");
|
|
||||||
}
|
}
|
||||||
result[i] = line.substring(0);
|
result[i] = line.substring(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import static org.junit.Assert.fail;
|
|||||||
|
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Log;
|
|
||||||
import android.util.LongSparseArray;
|
import android.util.LongSparseArray;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
@@ -35,7 +34,6 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -869,12 +867,34 @@ public class WatcherTest {
|
|||||||
mSeed = seed;
|
mSeed = seed;
|
||||||
mRandom = new Random(mSeed);
|
mRandom = new Random(mSeed);
|
||||||
}
|
}
|
||||||
public int index() {
|
public int next() {
|
||||||
return mRandom.nextInt(50000);
|
return mRandom.nextInt(50000);
|
||||||
}
|
}
|
||||||
public void reset() {
|
public void reset() {
|
||||||
mRandom.setSeed(mSeed);
|
mRandom.setSeed(mSeed);
|
||||||
}
|
}
|
||||||
|
// This is an inefficient way to know if a value appears in an array.
|
||||||
|
private boolean contains(int[] s, int length, int k) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (s[i] == k) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public int[] indexes(int size) {
|
||||||
|
reset();
|
||||||
|
int[] r = new int[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int key = next();
|
||||||
|
// Ensure the list of indices are unique.
|
||||||
|
while (contains(r, i, key)) {
|
||||||
|
key = next();
|
||||||
|
}
|
||||||
|
r[i] = key;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a value based on the row and column. The algorithm tries to avoid simple
|
// Return a value based on the row and column. The algorithm tries to avoid simple
|
||||||
@@ -883,28 +903,8 @@ public class WatcherTest {
|
|||||||
return (((row * 4 + col) % 3)& 1) == 1;
|
return (((row * 4 + col) % 3)& 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an inefficient way to know if a value appears in an array.
|
// Fill a matrix
|
||||||
private final boolean contains(int[] s, int length, int k) {
|
private void fill(WatchedSparseBooleanMatrix matrix, int size, int[] indexes) {
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
if (s[i] == k) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void matrixTest(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
|
|
||||||
indexer.reset();
|
|
||||||
int[] indexes = new int[size];
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
int key = indexer.index();
|
|
||||||
// Ensure the list of indices are unique.
|
|
||||||
while (contains(indexes, i, key)) {
|
|
||||||
key = indexer.index();
|
|
||||||
}
|
|
||||||
indexes[i] = key;
|
|
||||||
}
|
|
||||||
// Set values in the matrix.
|
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
int row = indexes[i];
|
int row = indexes[i];
|
||||||
for (int j = 0; j < size; j++) {
|
for (int j = 0; j < size; j++) {
|
||||||
@@ -913,21 +913,39 @@ public class WatcherTest {
|
|||||||
matrix.put(row, col, want);
|
matrix.put(row, col, want);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(matrix.size(), size);
|
// Verify the content of a matrix. This asserts on mismatch. Selected indices may
|
||||||
|
// have been deleted.
|
||||||
// Read back and verify
|
private void verify(WatchedSparseBooleanMatrix matrix, int[] indexes, boolean[] absent) {
|
||||||
for (int i = 0; i < matrix.size(); i++) {
|
for (int i = 0; i < matrix.size(); i++) {
|
||||||
int row = indexes[i];
|
int row = indexes[i];
|
||||||
for (int j = 0; j < matrix.size(); j++) {
|
for (int j = 0; j < matrix.size(); j++) {
|
||||||
int col = indexes[j];
|
int col = indexes[j];
|
||||||
boolean want = cellValue(i, j);
|
if (absent != null && (absent[i] || absent[j])) {
|
||||||
boolean actual = matrix.get(row, col);
|
boolean want = false;
|
||||||
String msg = String.format("matrix(%d:%d, %d:%d) == %s, expected %s",
|
String msg = String.format("matrix(%d:%d, %d:%d) (deleted)", i, row, j, col);
|
||||||
i, row, j, col, actual, want);
|
assertEquals(msg, matrix.get(row, col), false);
|
||||||
assertEquals(msg, actual, want);
|
assertEquals(msg, matrix.get(row, col, false), false);
|
||||||
|
assertEquals(msg, matrix.get(row, col, true), true);
|
||||||
|
} else {
|
||||||
|
boolean want = cellValue(i, j);
|
||||||
|
String msg = String.format("matrix(%d:%d, %d:%d)", i, row, j, col);
|
||||||
|
assertEquals(msg, matrix.get(row, col), want);
|
||||||
|
assertEquals(msg, matrix.get(row, col, false), want);
|
||||||
|
assertEquals(msg, matrix.get(row, col, true), want);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void matrixGrow(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
|
||||||
|
int[] indexes = indexer.indexes(size);
|
||||||
|
|
||||||
|
// Set values in the matrix, then read back and verify.
|
||||||
|
fill(matrix, size, indexes);
|
||||||
|
assertEquals(matrix.size(), size);
|
||||||
|
verify(matrix, indexes, null);
|
||||||
|
|
||||||
// Test the keyAt/indexOfKey methods
|
// Test the keyAt/indexOfKey methods
|
||||||
for (int i = 0; i < matrix.size(); i++) {
|
for (int i = 0; i < matrix.size(); i++) {
|
||||||
@@ -936,17 +954,101 @@ public class WatcherTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void matrixDelete(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
|
||||||
|
int[] indexes = indexer.indexes(size);
|
||||||
|
fill(matrix, size, indexes);
|
||||||
|
|
||||||
|
// Delete a bunch of rows. Verify that reading back results in false and that
|
||||||
|
// contains() is false. Recreate the rows and verify that all cells (other than
|
||||||
|
// the one just created) are false.
|
||||||
|
boolean[] absent = new boolean[size];
|
||||||
|
for (int i = 0; i < size; i += 13) {
|
||||||
|
matrix.deleteKey(indexes[i]);
|
||||||
|
absent[i] = true;
|
||||||
|
}
|
||||||
|
verify(matrix, indexes, absent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void matrixShrink(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
|
||||||
|
int[] indexes = indexer.indexes(size);
|
||||||
|
fill(matrix, size, indexes);
|
||||||
|
|
||||||
|
int initialCapacity = matrix.capacity();
|
||||||
|
|
||||||
|
// Delete every other row, remembering which rows were deleted. The goal is to
|
||||||
|
// make room for compaction.
|
||||||
|
boolean[] absent = new boolean[size];
|
||||||
|
for (int i = 0; i < size; i += 2) {
|
||||||
|
matrix.deleteKey(indexes[i]);
|
||||||
|
absent[i] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix.compact();
|
||||||
|
int finalCapacity = matrix.capacity();
|
||||||
|
assertTrue("Matrix shrink", initialCapacity > finalCapacity);
|
||||||
|
assertTrue("Matrix shrink", finalCapacity - matrix.size() < matrix.STEP);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWatchedSparseBooleanMatrix() {
|
public void testWatchedSparseBooleanMatrix() {
|
||||||
final String name = "WatchedSparseBooleanMatrix";
|
final String name = "WatchedSparseBooleanMatrix";
|
||||||
|
|
||||||
// The first part of this method tests the core matrix functionality. The second
|
// Test the core matrix functionality. The three tess are meant to test various
|
||||||
// part tests the watchable behavior. The third part tests the snappable
|
// combinations of auto-grow.
|
||||||
// behavior.
|
|
||||||
IndexGenerator indexer = new IndexGenerator(3);
|
IndexGenerator indexer = new IndexGenerator(3);
|
||||||
matrixTest(new WatchedSparseBooleanMatrix(), 10, indexer);
|
matrixGrow(new WatchedSparseBooleanMatrix(), 10, indexer);
|
||||||
matrixTest(new WatchedSparseBooleanMatrix(1000), 500, indexer);
|
matrixGrow(new WatchedSparseBooleanMatrix(1000), 500, indexer);
|
||||||
matrixTest(new WatchedSparseBooleanMatrix(1000), 2000, indexer);
|
matrixGrow(new WatchedSparseBooleanMatrix(1000), 2000, indexer);
|
||||||
|
matrixDelete(new WatchedSparseBooleanMatrix(), 500, indexer);
|
||||||
|
matrixShrink(new WatchedSparseBooleanMatrix(), 500, indexer);
|
||||||
|
|
||||||
|
// Test Watchable behavior.
|
||||||
|
WatchedSparseBooleanMatrix matrix = new WatchedSparseBooleanMatrix();
|
||||||
|
WatchableTester tester = new WatchableTester(matrix, name);
|
||||||
|
tester.verify(0, "Initial array - no registration");
|
||||||
|
matrix.put(INDEX_A, INDEX_A, true);
|
||||||
|
tester.verify(0, "Updates with no registration");
|
||||||
|
tester.register();
|
||||||
|
tester.verify(0, "Updates with no registration");
|
||||||
|
matrix.put(INDEX_A, INDEX_B, true);
|
||||||
|
tester.verify(1, "Single cell assignment");
|
||||||
|
matrix.put(INDEX_A, INDEX_B, true);
|
||||||
|
tester.verify(2, "Single cell assignment - same value");
|
||||||
|
matrix.put(INDEX_C, INDEX_B, true);
|
||||||
|
tester.verify(3, "Single cell assignment");
|
||||||
|
matrix.deleteKey(INDEX_B);
|
||||||
|
tester.verify(4, "Delete key");
|
||||||
|
assertEquals(matrix.get(INDEX_B, INDEX_C), false);
|
||||||
|
assertEquals(matrix.get(INDEX_B, INDEX_C, false), false);
|
||||||
|
assertEquals(matrix.get(INDEX_B, INDEX_C, true), true);
|
||||||
|
|
||||||
|
matrix.clear();
|
||||||
|
tester.verify(5, "Clear");
|
||||||
|
assertEquals(matrix.size(), 0);
|
||||||
|
fill(matrix, 10, indexer.indexes(10));
|
||||||
|
int[] keys = matrix.keys();
|
||||||
|
assertEquals(keys.length, matrix.size());
|
||||||
|
for (int i = 0; i < matrix.size(); i++) {
|
||||||
|
assertEquals(matrix.keyAt(i), keys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchedSparseBooleanMatrix a = new WatchedSparseBooleanMatrix();
|
||||||
|
matrixGrow(a, 10, indexer);
|
||||||
|
assertEquals(a.size(), 10);
|
||||||
|
WatchedSparseBooleanMatrix b = new WatchedSparseBooleanMatrix();
|
||||||
|
matrixGrow(b, 10, indexer);
|
||||||
|
assertEquals(b.size(), 10);
|
||||||
|
assertEquals(a.equals(b), true);
|
||||||
|
int rowIndex = b.keyAt(3);
|
||||||
|
int colIndex = b.keyAt(4);
|
||||||
|
b.put(rowIndex, colIndex, !b.get(rowIndex, colIndex));
|
||||||
|
assertEquals(a.equals(b), false);
|
||||||
|
|
||||||
|
// Test Snappable behavior.
|
||||||
|
WatchedSparseBooleanMatrix s = a.snapshot();
|
||||||
|
assertEquals(a.equals(s), true);
|
||||||
|
a.put(rowIndex, colIndex, !a.get(rowIndex, colIndex));
|
||||||
|
assertEquals(a.equals(s), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user