org.apache.mahout.math.bitvector.BitMatrix Maven / Gradle / Ivy
Show all versions of mahout-collections Show documentation
/*
Copyright 1999 CERN - European Organization for Nuclear Research.
Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose
is hereby granted without fee, provided that the above copyright notice appear in all copies and
that both that copyright notice and this permission notice appear in supporting documentation.
CERN makes no representations about the suitability of this software for any purpose.
It is provided "as is" without expressed or implied warranty.
*/
package org.apache.mahout.math.bitvector;
import org.apache.mahout.math.PersistentObject;
import java.awt.Rectangle;
/**
* Fixed sized (non resizable) n*m bit matrix.
* A bit matrix has a number of columns and rows, which are assigned upon instance construction - The matrix's size is then columns()*rows().
* Bits are accessed via (column,row) coordinates.
*
* Individual bits can be examined, set, or cleared.
* Rectangular parts (boxes) can quickly be extracted, copied and replaced.
* Quick iteration over boxes is provided by optimized internal iterators (forEach() methods).
* One BitMatrix
may be used to modify the contents of another
* BitMatrix
through logical AND, OR, XOR and other similar operations.
*
* Legal coordinates range from [0,0] to [columns()-1,rows()-1].
* Any attempt to access a bit at a coordinate column<0 || column>=columns() || row<0 || row>=rows() will throw an IndexOutOfBoundsException.
* Operations involving two bit matrices (like AND, OR, XOR, etc.) will throw an IllegalArgumentException if both bit matrices do not have the same number of columns and rows.
*
* If you need extremely quick access to individual bits: Although getting and setting individual bits with methods get(...) and put(...) is quick, it is even quicker (but not safe) to use getQuick(...) and putQuick(...).
*
* Note that this implementation is not synchronized.
*
* @see BitVector
* @see QuickBitVector
* @see java.util.BitSet
* @deprecated until unit tests have been written
*/
/** @deprecated until unit tests are in place. Until this time, this class/interface is unsupported. */
@Deprecated
public class BitMatrix extends PersistentObject {
private int columns;
private int rows;
/*
* The bits of this matrix.
* bits are stored in row major, i.e.
* bitIndex==row*columns + column
* columnOf(bitIndex)==bitIndex%columns
* rowOf(bitIndex)==bitIndex/columns
*/
private long[] bits;
/**
* Constructs a bit matrix with a given number of columns and rows. All bits are initially false.
*
* @param columns the number of columns the matrix shall have.
* @param rows the number of rows the matrix shall have.
* @throws IllegalArgumentException if columns < 0 || rows < 0.
*/
public BitMatrix(int columns, int rows) {
elements(QuickBitVector.makeBitVector(columns * rows, 1), columns, rows);
}
/**
* Performs a logical AND of the receiver with another bit matrix. The receiver is modified so that a bit in it
* has the value true
if and only if it already had the value true
and the corresponding bit
* in the other bit matrix argument has the value true
.
*
* @param other a bit matrix.
* @throws IllegalArgumentException if columns() != other.columns() || rows() != other.rows().
*/
public void and(BitMatrix other) {
checkDimensionCompatibility(other);
toBitVector().and(other.toBitVector());
}
/**
* Clears all of the bits in receiver whose corresponding bit is set in the other bit matrix. In other words,
* determines the difference (A\B) between two bit matrices.
*
* @param other a bit matrix with which to mask the receiver.
* @throws IllegalArgumentException if columns() != other.columns() || rows() != other.rows().
*/
public void andNot(BitMatrix other) {
checkDimensionCompatibility(other);
toBitVector().andNot(other.toBitVector());
}
/**
* Returns the number of bits currently in the true state. Optimized for speed. Particularly quick if the
* receiver is either sparse or dense.
*/
public int cardinality() {
return toBitVector().cardinality();
}
/** Sanity check for operations requiring matrices with the same number of columns and rows. */
protected void checkDimensionCompatibility(BitMatrix other) {
if (columns != other.columns() || rows != other.rows()) {
throw new IllegalArgumentException(
"Incompatible dimensions: (columns,rows)=(" + columns + ',' + rows + "), (other.columns,other.rows)=(" +
other.columns() + ',' + other.rows() + ')');
}
}
/** Clears all bits of the receiver. */
public void clear() {
toBitVector().clear();
}
/**
* Cloning this BitMatrix
produces a new BitMatrix
that is equal to it. The clone of the bit
* matrix is another bit matrix that has exactly the same bits set to true
as this bit matrix and the
* same number of columns and rows.
*
* @return a clone of this bit matrix.
*/
@Override
public Object clone() {
BitMatrix clone = (BitMatrix) super.clone();
if (this.bits != null) {
clone.bits = this.bits.clone();
}
return clone;
}
/** Returns the number of columns of the receiver. */
public int columns() {
return columns;
}
/** Checks whether the receiver contains the given box. */
protected void containsBox(int column, int row, int width, int height) {
if (column < 0 || column + width > columns || row < 0 || row + height > rows) {
throw new IndexOutOfBoundsException(
"column:" + column + ", row:" + row + " ,width:" + width + ", height:" + height);
}
}
/**
* Returns a shallow clone of the receiver; calls clone()
and casts the result.
*
* @return a shallow clone of the receiver.
*/
public BitMatrix copy() {
return (BitMatrix) clone();
}
protected long[] elements() {
return bits;
}
/**
* You normally need not use this method. Use this method only if performance is critical. Sets the bit matrix's
* backing bits, columns and rows. WARNING: For efficiency reasons and to keep memory usage low, the array
* is not copied. So if subsequently you modify the specified array directly via the [] operator, be sure you know
* what you're doing.
*
* @throws IllegalArgumentException if columns < 0 || rows < 0 || columns*rows > bits.length*64
*/
protected void elements(long[] bits, int columns, int rows) {
if (columns < 0 || rows < 0 || columns * rows > bits.length * QuickBitVector.BITS_PER_UNIT) {
throw new IllegalArgumentException();
}
this.bits = bits;
this.columns = columns;
this.rows = rows;
}
/**
* Compares this object against the specified object. The result is true
if and only if the argument is
* not null
and is a BitMatrix
object that has the same number of columns and rows as the
* receiver and that has exactly the same bits set to true
as the receiver.
*
* @param obj the object to compare with.
* @return true
if the objects are the same; false
otherwise.
*/
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof BitMatrix)) {
return false;
}
if (this == obj) {
return true;
}
BitMatrix other = (BitMatrix) obj;
if (columns != other.columns() || rows != other.rows()) {
return false;
}
return toBitVector().equals(other.toBitVector());
}
/**
* Applies a procedure to each coordinate that holds a bit in the given state. Iterates rowwise downwards from
* [columns()-1,rows()-1] to [0,0]. Useful, for example, if you want to copy bits into an image or somewhere else.
* Optimized for speed. Particularly quick if one of the following conditions holds
- state==true and
* the receiver is sparse (cardinality() is small compared to size()).
- state==false and
* the receiver is dense (cardinality() is large compared to size()).
*
* @param state element to search for.
* @param procedure a procedure object taking as first argument the current column and as second argument the current
* row. Stops iteration if the procedure returns false, otherwise continues.
* @return false if the procedure stopped before all elements where iterated over, true otherwise.
*/
public boolean forEachCoordinateInState(boolean state, org.apache.mahout.math.function.IntIntProcedure procedure) {
/*
this is equivalent to the low level version below, apart from that it iterates in the reverse oder and is slower.
if (size()==0) return true;
BitVector vector = toBitVector();
return vector.forEachIndexFromToInState(0,size()-1,state,
new IntFunction() {
public boolean apply(int index) {
return function.apply(index%columns, index/columns);
}
}
);
*/
//low level implementation for speed.
if (size() == 0) {
return true;
}
BitVector vector = new BitVector(bits, size());
long[] theBits = bits;
int column = columns - 1;
int row = rows - 1;
// for each coordinate of bits of partial unit
long val = theBits[bits.length - 1];
for (int j = vector.numberOfBitsInPartialUnit(); --j >= 0;) {
long mask = val & (1L << j);
if ((state && (mask != 0L)) || ((!state) && (mask == 0L))) {
if (!procedure.apply(column, row)) {
return false;
}
}
if (--column < 0) {
column = columns - 1;
--row;
}
}
// for each coordinate of bits of full units
long comparator;
if (state) {
comparator = 0L;
} else {
comparator = ~0L;
} // all 64 bits set
int bitsPerUnit = QuickBitVector.BITS_PER_UNIT;
for (int i = vector.numberOfFullUnits(); --i >= 0;) {
val = theBits[i];
if (val != comparator) {
// at least one element within current unit matches.
// iterate over all bits within current unit.
if (state) {
for (int j = bitsPerUnit; --j >= 0;) {
if (((val & (1L << j))) != 0L) {
if (!procedure.apply(column, row)) {
return false;
}
}
if (--column < 0) {
column = columns - 1;
--row;
}
}
} else { // unrolled comparison for speed.
for (int j = bitsPerUnit; --j >= 0;) {
if (((val & (1L << j))) == 0L) {
if (!procedure.apply(column, row)) {
return false;
}
}
if (--column < 0) {
column = columns - 1;
--row;
}
}
}
} else { // no element within current unit matches --> skip unit
column -= bitsPerUnit;
if (column < 0) {
// avoid implementation with *, /, %
column += bitsPerUnit;
for (int j = bitsPerUnit; --j >= 0;) {
if (--column < 0) {
column = columns - 1;
--row;
}
}
}
}
}
return true;
}
/**
* Returns from the receiver the value of the bit at the specified coordinate. The value is true if this bit
* is currently set; otherwise, returns false.
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @return the value of the bit at the specified coordinate.
* @throws IndexOutOfBoundsException if column<0 || column>=columns() || row<0 || row>=rows()
*/
public boolean get(int column, int row) {
if (column < 0 || column >= columns || row < 0 || row >= rows) {
throw new IndexOutOfBoundsException("column:" + column + ", row:" + row);
}
return QuickBitVector.get(bits, row * columns + column);
}
/**
* Returns from the receiver the value of the bit at the specified coordinate; WARNING: Does not check
* preconditions. The value is true if this bit is currently set; otherwise, returns false.
*
* Provided with invalid parameters this method may return invalid values without throwing any exception. You
* should only use this method when you are absolutely sure that the coordinate is within bounds. Precondition
* (unchecked): column>=0 && column<columns() && row>=0 && row<rows().
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @return the value of the bit at the specified coordinate.
*/
public boolean getQuick(int column, int row) {
return QuickBitVector.get(bits, row * columns + column);
}
/** Returns a hash code value for the receiver. */
public int hashCode() {
return toBitVector().hashCode();
}
/** Performs a logical NOT on the bits of the receiver. */
public void not() {
toBitVector().not();
}
/**
* Performs a logical OR of the receiver with another bit matrix. The receiver is modified so that a bit in it
* has the value true
if and only if it either already had the value true
or the
* corresponding bit in the other bit matrix argument has the value true
.
*
* @param other a bit matrix.
* @throws IllegalArgumentException if columns() != other.columns() || rows() != other.rows().
*/
public void or(BitMatrix other) {
checkDimensionCompatibility(other);
toBitVector().or(other.toBitVector());
}
/**
* Constructs and returns a new matrix with width columns and height rows which is a copy of the
* contents of the given box. The box ranges from [column,row] to [column+width-1,row+height-1], all
* inclusive.
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @param width the width of the box.
* @param height the height of the box.
* @throws IndexOutOfBoundsException if column<0 || column+width>columns() || row<0 ||
* row+height>rows()
*/
public BitMatrix part(int column, int row, int width, int height) {
if (column < 0 || column + width > columns || row < 0 || row + height > rows) {
throw new IndexOutOfBoundsException(
"column:" + column + ", row:" + row + " ,width:" + width + ", height:" + height);
}
if (width <= 0 || height <= 0) {
return new BitMatrix(0, 0);
}
BitMatrix subMatrix = new BitMatrix(width, height);
subMatrix.replaceBoxWith(0, 0, width, height, this, column, row);
return subMatrix;
}
/**
* Sets the bit at the specified coordinate to the state specified by value.
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @param value the value of the bit to be copied into the specified coordinate.
* @throws IndexOutOfBoundsException if column<0 || column>=columns() || row<0 || row>=rows()
*/
public void put(int column, int row, boolean value) {
if (column < 0 || column >= columns || row < 0 || row >= rows) {
throw new IndexOutOfBoundsException("column:" + column + ", row:" + row);
}
QuickBitVector.put(bits, row * columns + column, value);
}
/**
* Sets the bit at the specified coordinate to the state specified by value; WARNING: Does not check
* preconditions.
*
*
Provided with invalid parameters this method may return invalid values without throwing any exception. You
* should only use this method when you are absolutely sure that the coordinate is within bounds. Precondition
* (unchecked): column>=0 && column<columns() && row>=0 && row<rows().
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @param value the value of the bit to be copied into the specified coordinate.
*/
public void putQuick(int column, int row, boolean value) {
QuickBitVector.put(bits, row * columns + column, value);
}
/**
* Replaces a box of the receiver with the contents of another matrix's box. The source box ranges from
* [sourceColumn,sourceRow] to [sourceColumn+width-1,sourceRow+height-1], all inclusive. The
* destination box ranges from [column,row] to [column+width-1,row+height-1], all inclusive. Does
* nothing if width <= 0 || height <= 0. If source==this and the source and destination box
* intersect in an ambiguous way, then replaces as if using an intermediate auxiliary copy of the receiver.
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @param width the width of the box.
* @param height the height of the box.
* @param source the source matrix to copy from(may be identical to the receiver).
* @param sourceColumn the index of the source column-coordinate.
* @param sourceRow the index of the source row-coordinate.
* @throws IndexOutOfBoundsException if column<0 || column+width>columns() || row<0 ||
* row+height>rows()
* @throws IndexOutOfBoundsException if sourceColumn<0 || sourceColumn+width>source.columns() ||
* sourceRow<0 || sourceRow+height>source.rows()
*/
public void replaceBoxWith(int column, int row, int width, int height, BitMatrix source, int sourceColumn,
int sourceRow) {
this.containsBox(column, row, width, height);
source.containsBox(sourceColumn, sourceRow, width, height);
if (width <= 0 || height <= 0) {
return;
}
if (source == this) {
Rectangle destRect = new Rectangle(column, row, width, height);
Rectangle sourceRect = new Rectangle(sourceColumn, sourceRow, width, height);
if (destRect.intersects(sourceRect)) { // dangerous intersection
source = source.copy();
}
}
BitVector sourceVector = source.toBitVector();
BitVector destVector = this.toBitVector();
int sourceColumns = source.columns();
for (; --height >= 0; row++, sourceRow++) {
int offset = row * columns + column;
int sourceOffset = sourceRow * sourceColumns + sourceColumn;
destVector.replaceFromToWith(offset, offset + width - 1, sourceVector, sourceOffset);
}
}
/**
* Sets the bits in the given box to the state specified by value. The box ranges from [column,row]
* to [column+width-1,row+height-1], all inclusive. (Does nothing if width <= 0 || height <=
* 0).
*
* @param column the index of the column-coordinate.
* @param row the index of the row-coordinate.
* @param width the width of the box.
* @param height the height of the box.
* @param value the value of the bit to be copied into the bits of the specified box.
* @throws IndexOutOfBoundsException if column<0 || column+width>columns() || row<0 ||
* row+height>rows()
*/
public void replaceBoxWith(int column, int row, int width, int height, boolean value) {
containsBox(column, row, width, height);
if (width <= 0 || height <= 0) {
return;
}
BitVector destVector = this.toBitVector();
for (; --height >= 0; row++) {
int offset = row * columns + column;
destVector.replaceFromToWith(offset, offset + width - 1, value);
}
}
/** Returns the number of rows of the receiver. */
public int rows() {
return rows;
}
/** Returns the size of the receiver which is columns()*rows(). */
public int size() {
return columns * rows;
}
/**
* Converts the receiver to a bitvector. In many cases this method only makes sense on one-dimensional matrices.
* WARNING: The returned bitvector and the receiver share the same backing bits. Modifying either of
* them will affect the other. If this behaviour is not what you want, you should first use copy() to make
* sure both objects use separate internal storage.
*/
public BitVector toBitVector() {
return new BitVector(bits, size());
}
/** Returns a (very crude) string representation of the receiver. */
public String toString() {
return toBitVector().toString();
}
/**
* Performs a logical XOR of the receiver with another bit matrix. The receiver is modified so that a bit in it
* has the value true
if and only if one of the following statements holds:
- The bit initially
* has the value
true
, and the corresponding bit in the argument has the value false
.
* - The bit initially has the value
false
, and the corresponding bit in the argument has the value
* true
.
*
* @param other a bit matrix.
* @throws IllegalArgumentException if columns() != other.columns() || rows() != other.rows().
*/
public void xor(BitMatrix other) {
checkDimensionCompatibility(other);
toBitVector().xor(other.toBitVector());
}
}