
org.factcenter.qilin.util.BitMatrix Maven / Gradle / Ivy
package org.factcenter.qilin.util;
import org.factcenter.qilin.comm.Sendable;
import org.factcenter.qilin.comm.SendableInput;
import org.factcenter.qilin.comm.SendableOutput;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
/**
* Represents a matrix of bits with corresponding operations.
* @author talm
*
*/
public final class BitMatrix implements Sendable, Cloneable {
/**
* The actual bits (packed). Bits are stored in row order, and every row is padded to byte boundaries
* (i.e., if there are 15 bits per row, each row is 2 bytes long). Within each byte, the LSB is bit 0
* and the MSB bit 7.
*/
byte[] bits;
/**
* Number of columns.
*/
int numCols;
/**
* Number of rows.
*/
int numRows;
/**
* true iff the number of columns is divisible by 8 or any remaining bits in the final byte of each row are zeroes.
*/
boolean zeroPadded;
/**
* If the matrix is sharing its backing array with another, and should copy the relevant bits
* during write.
*/
boolean copyOnWrite;
/**
* Actual byte offset in {@link #bits} where array starts.
*/
transient int rowOffs;
/**
* Number of bytes in a row.
* This is usually {@link #numCols} / 8 rounded up, but can be more
* (in which case the extra bytes in the row are padding and should be ignored).
*/
transient int bytesPerRow;
/**
* How many bytes are used for a row with a specified number of columns.
* @param numCols
*/
public final int getUsedBytesPerRow(int numCols) {
return (numCols / 8) + (numCols % 8 != 0 ? 1 : 0);
}
/**
* Return the number of bytes actually used in row.
*/
public final int getUsedBytesPerRow() {
return getUsedBytesPerRow(numCols);
}
/**
* Initialize bits array and precomputed values.
*/
private void allocate() {
bytesPerRow = getUsedBytesPerRow();
bits = new byte[bytesPerRow * numRows];
rowOffs = 0;
zeroPadded = true;
copyOnWrite = false;
}
/**
* Don't ever call this explicitly: used only for deserialization!
*/
public BitMatrix() {
// Used only for deserialization
}
/**
* Create a new BitMatrix with numRows rows and numCols columns. To create a bit vector, set numRows to 1.
* @param numCols
* @param numRows
*/
public BitMatrix(int numCols, int numRows) {
this.numCols = numCols;
this.numRows = numRows;
allocate();
}
/**
* Utility constructor for bit vector.
*/
public BitMatrix(int numCols) {
this(numCols, 1);
}
/**
* Create a vector by wrapping a byte array.
* Note that the backing array is not copied---changes
* to this array will be reflected in the vector, but copy-on-write
* semantics are used for BitMatrix operations..
*/
public BitMatrix(byte[] bits, int offs) {
this.bits = bits;
this.rowOffs = offs;
bytesPerRow = bits.length - offs;
numCols = bytesPerRow * 8;
numRows = 1;
copyOnWrite = true;
zeroPad();
}
/**
* Create a matrix by wrapping a byte array.
* Note that the backing array is not copied---changes
* to this array will be reflected in the matrix.
* @param bits
* @param numCols
* @param numRows
*/
public BitMatrix(byte[] bits, int rowOffs, int numCols, int numRows) {
this.bits = bits;
this.rowOffs = rowOffs;
this.numCols = numCols;
this.numRows = numRows;
if ((bits.length - rowOffs) * 8 < numCols * numRows)
throw new RuntimeException("Invalid BitMatrix size (numCols="+numCols+",bytesPerRow="+bytesPerRow+")");
bytesPerRow = getUsedBytesPerRow();
copyOnWrite = true;
zeroPad();
}
/**
* Create a matrix by wrapping a byte array.
* Note that the backing array is not copied---changes
* to this array will be reflected in the matrix unless the
* provided array is too small (in that case a new array will be allocated).
* @param bits
* @param numCols
* @param numRows
*/
public BitMatrix(byte[] bits, int rowOffs, int numCols, int numRows, int bytesPerRow, boolean copyOnWrite,
boolean zeroPadding) {
this.bits = bits;
this.rowOffs = rowOffs;
this.numCols = numCols;
this.numRows = numRows;
this.copyOnWrite = copyOnWrite;
this.bytesPerRow = bytesPerRow;
if (numCols > bytesPerRow * 8)
throw new RuntimeException("Invalid BitMatrix size (numCols="+numCols+",bytesPerRow="+bytesPerRow+")");
if ((bits.length - rowOffs) * 8 < numCols * numRows)
throw new RuntimeException("Invalid BitMatrix size (numCols="+numCols+",bytesPerRow="+bytesPerRow+")");
if (zeroPadding)
zeroPad();
else {
if (numCols % 8 != 0)
zeroPadded = false;
}
}
/**
* Create a new bitmatrix sharing a backing array with an existing one, and using copy-on-write semantics.
* @param bits
* @param rowOffs
* @param numCols
* @param numRows
* @param bytesPerRow
*/
public BitMatrix(byte[] bits, int rowOffs, int numCols, int numRows, int bytesPerRow) {
this(bits, rowOffs, numCols, numRows, bytesPerRow, true, true);
}
/**
* Return a new BitMatrix representing an (unsigned) integer value.
* The matrix contains a single row of width columnts, with bits stored in LSB-first order.
* @param value The value to represent
* @param width the width in bits.
*/
public static BitMatrix valueOf(long value, int width) {
BitMatrix b = new BitMatrix(width);
b.setBits(0, width, value);
return b;
}
public static BitMatrix valueOf(BigInteger value, int width) {
BitMatrix b = new BitMatrix(width);
byte[] valBits = value.toByteArray();
long sign = value.signum() < 0 ? -1 : 0;
// Note: we use the fact the setBits can handle writing to positions outside the
// BitMatrix by truncating.
for (int i = 0; i < width / 8 + 1; ++i) {
long valOctet;
if (i >= valBits.length) {
valOctet = sign;
} else {
valOctet = valBits[valBits.length - i - 1];
}
b.setBits(i * 8, 8, valOctet);
}
return b;
}
/**
* Return a bit matrix with all bits set.
* @param width
*/
public static BitMatrix allOnes(int width) {
BitMatrix b = new BitMatrix(width);
for (int i = 0; i < b.bits.length; ++i) {
b.bits[i] = (byte) 0xff;
}
b.zeroPad();
return b;
}
final public long toInteger() {
return toInteger(getNumCols());
}
/**
* Convert the least-significant width bits of the first row in this matrix
* to an integer (well-defined only for width up to 64).
* @param width
*/
final public long toInteger(int width) {
if (numCols < width)
width = numCols;
int startIdx = getRowIndex(0);
long value = 0;
if (width % 8 != 0) {
long mask = ((1 << (width % 8)) - 1);
value = ((int) bits[startIdx + width / 8]) & mask;
}
for (int i = width / 8 - 1; i >= 0; --i) {
value <<= 8;
value |= ((int) bits[startIdx + i]) & 0xff;
}
return value;
}
/**
* Convert the entire vector (first row) into a BigInteger. The value is always treated as unsigned in little-endian
* order (LSB first).
*/
final public BigInteger toBigInteger() {
return toBigInteger(getNumCols());
}
/**
* Convert the least-significant width bits of the first row in
* this matrix to a BigInteger. The value is always treated as unsigned in little-endian
* order (LSB first).
*
* @param width
*/
final public BigInteger toBigInteger(int width) {
if (width > getNumCols())
width = getNumCols();
int startIdx = getRowIndex(0);
int byteLen = width / 8;
if (width % 8 != 0)
++byteLen;
byte[] bigBits = new byte[byteLen];
for (int i = 0; i < byteLen; ++i) {
bigBits[byteLen - i - 1] = bits[startIdx + i];
}
if (width % 8 != 0) {
int mask = (1 << (width % 8)) - 1;
bigBits[0] &= mask;
}
return new BigInteger(1, bigBits);
}
/**
* Clone the current BitMatrix into a byte array in packed form, starting from a given column.
* The destination array must have enough space to contain the data.
* @param dst
* @param colStart
* @param numCols
*/
final public void clonePacked(byte[] dst, int colStart, int numCols) {
if (colStart + numCols >= this.numCols)
numCols = this.numCols - colStart;
int packedRowLen = getUsedBytesPerRow(numCols);
if (colStart % 8 == 0) {
int lastColByte = colStart / 8 + numCols / 8;
int partialByteSize = numCols % 8;
for (int i = 0; i < getNumRows(); ++i) {
System.arraycopy(bits, getRowIndex(i) + colStart / 8, dst, i * packedRowLen, packedRowLen);
if (!zeroPadded && partialByteSize != 0) {
dst[i*packedRowLen+lastColByte] &= (1 << partialByteSize) - 1;
}
}
} else {
// Every byte needs to be shifted.
int colShift = colStart / 8;
int bitShift = colStart % 8;
int bitMask = (1 << bitShift) - 1;
for (int i = 0; i < getNumRows(); ++i) {
int rowStart = getRowIndex(i);
int dstRowStart = i * packedRowLen;
assert(dst != bits || dstRowStart <= rowStart);
for (int j = 0; j < packedRowLen; ++j) {
dst[dstRowStart + j] = (byte) ((bits[rowStart + colShift + j] & 0xff) >>> bitShift);
if (rowStart + colShift + j + 1 < bits.length)
dst[dstRowStart + j] |= (bits[rowStart + colShift + j + 1] & bitMask) << (8 - bitShift);
}
}
}
}
/**
* Reduce the number of columns in a row without allocating a new backing array.
*
* Note:The backing array after this operation may not be zero-padded, Moreover, operations that do zero-pad
* {@link #xorRow(int, BitMatrix, int)} and {@link #fillRandom(Random)})
* may change values outside the columns spanned by the submatrix.
*
* @param newNumCols
*/
public void subcolumns(int colStart, int newNumCols) {
if (colStart + newNumCols > numCols)
throw new RuntimeException("Can't increase numCols (numCols="+numCols+",newNumCols="+colStart + newNumCols+")");
if (colStart % 8 != 0) {
if (copyOnWrite) {
internalCopy();
} else {
// Shift bits in place
clonePacked(bits, colStart, newNumCols);
}
numCols = newNumCols;
rowOffs = 0;
bytesPerRow = getUsedBytesPerRow();
zeroPad();
return;
}
// We need to move the row offset to compensate for entire byte shifts
numCols = newNumCols;
rowOffs += colStart / 8;
if ((newNumCols + colStart) % 8 != 0)
zeroPadded = false;
}
/**
* Reduce the number of rows without allocating a new backing array.
* @param rowStart
* @param newNumRows
*/
public void subrows(int rowStart, int newNumRows) {
rowOffs = getRowIndex(rowStart);
numRows = Math.min(numRows - rowStart, newNumRows);
}
/**
* Copy the data from the current backing array to a new, packed array.
* @param colStart starting column to copy
* @param newNumCols number of columns to copy.
*/
private void internalCopy(int colStart, int newNumCols) {
if (colStart + newNumCols >= numCols)
newNumCols = numCols - colStart;
byte[] newBits = new byte[getNumRows() * getUsedBytesPerRow(newNumCols)];
clonePacked(newBits, colStart, newNumCols);
bits = newBits;
copyOnWrite = false;
rowOffs = 0;
numCols = newNumCols;
bytesPerRow = getUsedBytesPerRow();
zeroPadded = true;
}
/**
* Ensure that the backing array is not shared with another BitMatrix
*/
final public void internalCopy() {
if (copyOnWrite)
internalCopy(0, getNumCols());
}
/**
* Create a clone of this BitMatrix (backing array is copied).
* The new clone will be in packed format.
*/
final public BitMatrix clone() {
byte[] newbits = getPackedBits(true);
// No need to copy-on-write, we just created a brand-new bit array.
return new BitMatrix(newbits, 0, numCols, numRows, getUsedBytesPerRow(), false, true);
}
/**
* Return a sub-matrix with the same number of columns but a subset of the rows.
* The backing array is shared between the matrices (direct changes in one will appear in the other),
* but uses copy-on-write semantics (BitMatrix operations that modify the backing array will trigger
* a full copy).
* @param startRow Starting row (inclusive)
* @param numRows number of rows.
*/
public BitMatrix getSubMatrix(int startRow, int numRows) {
if (this.numRows - startRow < numRows)
throw new RuntimeException("Invalid submatrix size");
copyOnWrite = true; // we're sharing bits with our new submatrix.
return new BitMatrix(bits, getRowIndex(startRow), numCols, numRows, bytesPerRow);
}
/**
* Return a sub-matrix with the same number of rows but a subset of the columns.
* The backing array may be shared between the matrices (changes in one will appear in the other),
* or may be copied (if the start column isn't on a byte boundary).
*
* @param startCol Starting column (inclusive)
* @param newNumCols number of columns in new matrix
*/
public BitMatrix getSubMatrixCols(int startCol, int newNumCols) {
BitMatrix subMatrix;
if (startCol % 8 == 0) {
copyOnWrite = true; // we're sharing bits with our new submatrix.
subMatrix = new BitMatrix(bits, rowOffs, numCols, numRows, bytesPerRow, true, false);
subMatrix.subcolumns(startCol, newNumCols);
} else {
byte[] newBits = new byte[getNumRows() * getUsedBytesPerRow(newNumCols)];
clonePacked(newBits, startCol, newNumCols);
subMatrix = new BitMatrix(newBits, 0, newNumCols, numRows, getUsedBytesPerRow(newNumCols), false, true);
}
return subMatrix;
}
public boolean isZeroPadded() {
return zeroPadded;
}
/**
* Zero all the padding bits (if any).
*/
public void zeroPad() {
internalCopy();
if (numCols % 8 != 0) {
int lastColByte = numCols / 8;
int partialByteSize = numCols % 8;
// Zero padding bytes in all rows.
for (int i = 0; i < getNumRows(); ++i) {
int row = getRowIndex(i);
// Zero partial final byte
bits[row + lastColByte] &= (1 << partialByteSize) - 1;
}
}
zeroPadded = true;
}
/**
* Fill the matrix with random bits.
* Note: currently fills the entire backing array -- so will affect "parents" of submatrices.
* @param rand
*/
public void fillRandom(Random rand) {
if (copyOnWrite || getRowIndex(0) != 0 || (bits.length > getNumRows() * bytesPerRow) ||
bytesPerRow > getUsedBytesPerRow()) {
internalCopy(0, getNumCols());
}
rand.nextBytes(bits);
zeroPad();
}
/**
* Return the backing array.
*/
public final byte[] getBackingArray() {
return bits;
}
/**
* Return the array in packed format.
* The size of the array is guaranteed to be {@link #getNumRows()}*ceil({@link #getNumCols()}/8):
* (Each row is byte-aligned). If the backing array is already in packed format and zero-padded,
* this method will not copy the array (otherwise a new array will be allocated and the bits copied)
*/
public final byte[] getPackedBits(boolean forceCopy) {
int packedRowLen = getUsedBytesPerRow();
if (!forceCopy && zeroPadded && rowOffs == 0 && packedRowLen == getRowLen() && getRowIndex(getNumRows()) == bits.length)
return bits;
byte[] newArr = new byte[getNumRows() * packedRowLen];
clonePacked(newArr, 0, getNumCols());
return newArr;
}
public final int getNumRows() {
return numRows;
}
public final int getNumCols() {
return numCols;
}
/**
* Return the byte index in the backing array for the beginning of a row.
* @param row
*/
public final int getRowIndex(int row) {
return rowOffs + row * bytesPerRow;
}
/**
* Return number of bytes in a row.
* Note that if the number of columns is not divisible by 8,
* the last byte of the row will be padded with zero bits.
*/
public final int getRowLen() {
return bytesPerRow;
}
/**
* Return a row as a {@link ByteBuffer} (by wrapping the backing array).
* @param row the row index.
*/
public final ByteBuffer getRow(int row) {
return ByteBuffer.wrap(bits, getRowIndex(row), bytesPerRow);
}
/**
* Return the bit at position (col, row) in the matrix.
* @param row the row index (zero based)
* @param col the column index (zero based)
* @return the bit (0 or 1)
*/
public final int getBit(int col, int row) {
int bitIdx = getRowIndex(row) + col / 8;
return (bits[bitIdx] >>> (col % 8)) & 1;
}
/**
* Return a word constructed from biLen bits beginning at position (col, row) in the matrix.
* The word is assumed to be packed LSB first
* @param row the row index (zero based)
* @param col the column index (zero based)
* @param bitLen length of the word (up to 64 bits)
* @return the word
*/
public final long getBits(int col, int row, int bitLen) {
int bitIdx = getRowIndex(row) + col / 8;
long retval = 0;
int colShift = col % 8;
int bitPos = 0;
int bytePos = 0;
if (colShift > 0) {
retval = (byte) ((bits[bitIdx] & 0xff) >>> colShift);
bitPos += (8 - colShift);
bytePos = 1;
}
while (bitPos < bitLen) {
retval |= (((long)bits[bitIdx + bytePos]) & 0xff) << bitPos;
bitPos += 8;
++bytePos;
}
assert(bitLen <= 64);
// hack: in this case, masking out will zero out the entire return value.
// this should be handled with arbitrary width integers.
if (bitLen == 64) {
return retval;
}
// Mask out any extra bits from not being on byte split.
retval &= (1L << bitLen) - 1;
return retval;
}
/**
* Get a bit from the first row (utility function for bit vectors).
* @param col
*/
public final int getBit(int col) {
return getBit(col, 0);
}
/**
* Get a several bits from the first row (utility function for bit vectors).
* @param col
* @param bitLen
*/
public final long getBits(int col, int bitLen) {
return getBits(col, 0, bitLen);
}
/**
* Set the bit at position (col, row) in the matrix
* @param row
* @param col
* @param bit
*/
public final void setBit(int col, int row, int bit) {
if (copyOnWrite)
internalCopy();
bit &= 1;
int byteIdx = getRowIndex(row) + (col / 8);
int bitIdx = col % 8;
bits[byteIdx] = (byte) ((bits[byteIdx] & ~(1 << bitIdx)) | (bit << bitIdx));
}
/**
* Write a word into the packed bit array. Word is written LSB first.
* If the word overflows the columns it is truncated.
* @param col Starting column
* @param row Starting row
* @param bitLen length of word in bits
* @param word word to write.
*/
public final void setBits(int col, int row, int bitLen, long word) {
if (copyOnWrite)
internalCopy();
if (bitLen + col > numCols)
bitLen = numCols - col;
int bitIdx = getRowIndex(row) + (col / 8);
int colShift = col % 8;
if (colShift != 0) {
int remBits = 8 - colShift;
if (bitLen < remBits)
remBits = bitLen;
int bitMask = (1 << remBits) - 1;
// Mask out the higher bits.
bits[bitIdx] &= ~(bitMask << colShift);
bits[bitIdx] |= (word & bitMask) << colShift;
word >>>= remBits;
bitLen -= remBits;
bitIdx++;
}
while (bitLen >= 8) {
bits[bitIdx] = (byte) (word & 0xff);
word >>>= 8;
++bitIdx;
bitLen -= 8;
}
if (bitLen > 0) {
int mask = (1 << bitLen) - 1;
bits[bitIdx] &= ~mask;
bits[bitIdx] |= (byte) (word & mask);
}
}
/**
* Copy all the bits (in row 0) from newBits into this BitMatrix, starting at column col (row 0)
* @param col
* @param newBits
*/
public final void setBits(int col, BitMatrix newBits) {
copyRow(col, newBits, 0, newBits.getNumCols());
}
/**
* Copy up to maxLen bits from newBits to this BitMatrix, starting at column col (row 0)
*/
public final void setBits(int col, int maxLen, BitMatrix newBits) {
copyRow(col, newBits, 0, maxLen);
}
/**
* Write a word in row 0 (convenience method for bit vectors).
* @param col
* @param bitLen
* @param word
*/
public final void setBits(int col, int bitLen, long word) {
setBits(col, 0, bitLen, word);
}
/**
* Convenience method for treating bit vector
* as a packed array of words.
* @param idx
* @param wordSize
*/
public final long getWord(int idx, int wordSize) {
return getBits(idx * wordSize, wordSize);
}
/**
* Convenience method for treating bit vector
* as a packed array of words.
* @param idx
* @param wordSize
*/
public final void setWord(int idx, int wordSize, long word) {
setBits(idx * wordSize, wordSize, word);
}
/**
* Xor the bit at position (col, row) in the matrix
* @param row
* @param col
* @param bit
*/
public final void xorBit(int col, int row, int bit) {
setBit(col, row, getBit(col, row) ^ bit);
}
/**
* Set the bit at pos col in the first row (utility function for bit vectors).
* @param col
* @param bit
*/
public final void setBit(int col, int bit) {
setBit(col, 0, bit);
}
/**
* Xor the bit at pos col in the first row (utility function for bit vectors).
* @param col
* @param bit
*/
public final void xorBit(int col, int bit) {
xorBit(col, 0, bit);
}
/**
* XOR a row of another matrix with a row of this matrix
* @param dstRow the row in this matrix
* @param src the other matrix whose row we XORing to this
* @param srcRow the row in src to xor.
*/
public final void xorRow(int dstRow, BitMatrix src, int srcRow) {
assert(numCols == src.numCols);
assert(getUsedBytesPerRow() <= src.getUsedBytesPerRow());
internalCopy();
int usedBytes = getUsedBytesPerRow();
int partialByte = getNumCols() % 8;
int bitMask = (1 << partialByte) - 1;
if (partialByte != 0)
--usedBytes;
int startPosA = getRowIndex(dstRow);
int startPosB = src.getRowIndex(srcRow);
for (int i = 0; i < usedBytes; ++i)
bits[startPosA + i] ^= src.bits[startPosB + i];
if (partialByte != 0) {
bits[startPosA + usedBytes] ^= src.bits[startPosB + usedBytes] & bitMask;
}
}
/**
* Overwrite part of this bitvector with part of another.
* If the source overflows the destination only a prefix will be copied.* @param dstRow the row in this matrix
* @param dstStart the first column that will be overwritten
* @param src the other matrix whose row we are copying
* @param srcStart the first column to read from
* @param srcLen the number of bits to copy.
*/
public final void copyRow(int dstStart, BitMatrix src, int srcStart, int srcLen) {
copyRow(0, dstStart, src, 0, srcStart, srcLen);
}
/**
* Copy part of a row from another matrix to part of a row in this matrix.
* If the source row overflows the destination row only a prefix will be copied.
* @param dstRow the row in this matrix
* @param dstStart the first column that will be overwritten
* @param src the other matrix whose row we are copying
* @param srcRow the row in src to copy.
* @param srcStart the first column to read from
* @param srcLen the number of bits to copy.
*/
public final void copyRow(int dstRow, int dstStart, BitMatrix src, int srcRow, int srcStart, int srcLen) {
internalCopy();
// Truncate if overflow
if (srcLen + dstStart > numCols)
srcLen = numCols - dstStart;
if (((dstStart % 8) | (srcStart % 8) | (srcLen % 8)) == 0) {
// Everything is cleanly on byte boundaries. Yay!
int dstPos = getRowIndex(dstRow) + dstStart / 8;
int srcPos = src.getRowIndex(srcRow) + srcStart / 8;
System.arraycopy(src.bits, srcPos, bits, dstPos, srcLen / 8);
} else {
// Do things inefficiently, bit by bit...
for (int i = 0; i < srcLen; ++i) {
setBit(dstStart + i, dstRow, src.getBit(srcStart + i, srcRow));
}
/*
* TODO: Complete efficient implementation that uses arraycopy for the
* the middle and bit operations for the edges.
// Compute location of first full byte
int dstBytePos = getRowIndex(dstRow) + dstStart / 8;
if (dstStart % 8 != 0)
++dstBytePos;
int srcFullLen = srcLen - (8 - (dstStart % 8));
int srcByteLen = srcFullLen / 8;
int srcBytePos = src.getRowIndex(srcRow) + ;
System.arraycopy(src.bits, srcPos, bits, dstPos, getUsedBytesPerRow());
// Number of full bytes to copy
int dstFullLen = srcLen / 8;
*/
}
}
/**
* Copy a row from another matrix to a row of this matrix.
* If the row is longer only a prefix will be copied.
* Note: this operation will zeropad the backing array.
*
* @param dstRow the row in this matrix
* @param src the other matrix whose row we are copying
* @param srcRow the row in src to copy.
*/
public final void copyRow(int dstRow, BitMatrix src, int srcRow) {
internalCopy();
int dstPos = getRowIndex(dstRow);
int srcPos = src.getRowIndex(srcRow);
int numBytes = Math.min(getUsedBytesPerRow(), src.getUsedBytesPerRow());
System.arraycopy(src.bits, srcPos, bits, dstPos, numBytes);
zeroPad();
}
/**
* Copy the bits from another wrapped bit-vector matrix into this matrix.
* Notes: this operation will zero-pad the backing array.
* if src vector is longer, only the prefix will be copied.
* @param src the matrix containing the bits to copy.
*/
public final void copyBits(BitMatrix src) {
if (copyOnWrite)
internalCopy();
int numBytes = Math.min(getUsedBytesPerRow(), src.getUsedBytesPerRow());
System.arraycopy(src.bits, src.getRowIndex(0), bits, getRowIndex(0), numBytes);
zeroPad();
}
/**
* Sets the elements of the bits byte array to zero.
*/
public final void reset(){
if (copyOnWrite) {
bits = new byte[getNumRows() * getUsedBytesPerRow()];
rowOffs = 0;
bytesPerRow = getUsedBytesPerRow();
copyOnWrite = false;
} else {
Arrays.fill(bits, getRowIndex(0), getRowIndex(0) + getUsedBytesPerRow() * getNumRows(), (byte) 0);
}
zeroPadded = true;
}
/**
* Xor an entire matrix. The matrix must have at least the same number of rows and columns.
* @param b
*/
public final void xor(BitMatrix b) {
internalCopy();
assert(getNumRows() <= b.getNumRows()) :
String.format("BitMatrix xor dest is smaller (%dx%d) than source (%dx%d)", getNumCols(),getNumRows(),b.getNumCols(), b.getNumRows());
assert(getNumCols() <= b.getNumCols()) :
String.format("BitMatrix xor dest is smaller (%dx%d) than source (%dx%d)", getNumCols(),getNumRows(),b.getNumCols(), b.getNumRows());
int used = getUsedBytesPerRow();
int partialByte = getNumCols() % 8;
if (partialByte != 0)
--used;
for (int i = 0; i < getNumRows(); ++i) {
int startA = getRowIndex(i);
int startB = b.getRowIndex(i);
for (int j = 0; j < used; ++j) {
bits[startA + j] ^= b.bits[startB + j];
}
if (partialByte != 0) {
int bitMask;
bitMask = (1 << partialByte) - 1;
bits[startA + used] ^= b.bits[startB + used] & bitMask;
}
}
}
/**
* Transpose the matrix
* @return the transpose.
*/
public final BitMatrix transpose() {
// TODO: Improve efficiency.
BitMatrix T = new BitMatrix(getNumRows(), getNumCols());
for (int i = 0; i < getNumRows(); ++i)
for (int j = 0; j < getNumCols(); ++j)
T.setBit(i, j, getBit(j, i));
return T;
}
@Override
public void readFrom(SendableInput in) throws IOException {
internalCopy();
numCols = in.readInt();
numRows = in.readInt();
allocate();
in.readFully(bits);
}
@Override
public void writeTo(SendableOutput out) throws IOException {
out.writeInt(numCols);
out.writeInt(numRows);
if (bytesPerRow > getUsedBytesPerRow()) {
// There are extra padding bytes at the end of each row.
// We'll write out the rows without them
for (int i = 0; i < numRows; ++i) {
out.write(bits, getRowIndex(i), getUsedBytesPerRow());
}
} else {
// We can write the entire buffer directly.
out.write(bits, rowOffs, numRows * bytesPerRow);
}
}
/**
* Test equality. Two bitmatrices are considered equal if their bits are equal (regardless of the contents
* of the backing array outside the "real" data.
*
* This is an inefficient test at the moment that works bit-by-bit.
* @param other
*/
@Override
public boolean equals(Object other) {
if (! (other instanceof BitMatrix))
return false;
BitMatrix b = (BitMatrix) other;
if (numRows != b.numRows || numCols != b.numCols)
return false;
for (int row = 0; row < numRows; ++row)
for (int col = 0; col < numCols; ++col)
if (getBit(col, row) != b.getBit(col, row))
return false;
return true;
}
@Override
public String toString() {
return String.format("0x%s[%d%s]", toBigInteger().toString(16), getNumCols(),
getNumRows() > 1 ? "x" + getNumRows() : "");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy