Skip to main content
Code Review

Return to Revisions

6 of 6
Commonmark migration

First of all: I am not a well mathematician, so I can not tell you, if all the cases are considered.

I can only make suggestions regarding your code style.


Basics

An enum in java can only have the values you define in it or null. The check

if (hungarianOperationMode != HungarianOperationMode.MAXIMIZE && hungarianOperationMode != HungarianOperationMode.MINIMIZE) 
 throw new IllegalArgumentException("Unexpected hungarian operation mode.");

could be simplified to

if (hungarianOperationMode == null) 
 throw new IllegalArgumentException("Unexpected hungarian operation mode.");

What I think is terrible code style, is this block of if/else:

if (inputMatrix == null) {
 throw new IllegalArgumentException("The input matrix is null");
} else if (hungarianOperationMode != HungarianOperationMode.MAXIMIZE && hungarianOperationMode != HungarianOperationMode.MINIMIZE) {
 throw new IllegalArgumentException("Unexpected hungarian operation mode.");
} else {
 this.height = inputMatrix.length;
 if (this.height < 1) {
 throw new IllegalArgumentException("The input matrix has no height");
 } else {
 for (int row=0; row<this.height; row++) {
 Long[] inputMatrixRow = inputMatrix[row];
 if (inputMatrixRow == null) {
 throw new IllegalArgumentException("The input matrix has null for row " + row);
 } else {
 int inputMatrixRowLength = inputMatrixRow.length;
 if (row == 0) {
 this.width = inputMatrixRowLength;
 } else if (this.width != inputMatrixRowLength) {
 throw new IllegalArgumentException("The input matrix is not rectangular");
 }
 }
 }

All these are only for input validation and I suggest you consider transforming those to using a simplyfied intercepting filter pattern as used in this example

Your input validation could look like this:

List<Rule> rules = new ArrayList<>();
rules.add( new MatrixNotNullRule () );
rules.add( new MatrixHasHeightRule () );
...
for ( Rule rule : rules )
{
 rule.validate( inputMatrix );
} 

where each rule implements the Rule interface:

public interface Rule{
void validate( Long[][] inputMatrix ) throws IllegalArgumentException;
}

and performs a single check:

public class MatrixNotNullRule implements Rule{
 @Override
 public void validate( Long[][] inputMatrix ) throws IllegalArgumentException
 {
 if (inputMatrix == null) {
 throw new IllegalArgumentException("The input matrix is null");
 }
}

Good practice

would be throwing a dedicated exception. Write your own exception and name it something like

HungarianException

or

HungarianOperationException

It may inherit from an IllegalArgumentException or whatever, but I should not be one.


Extract your exception messages

into string constants.

public class MatrixNotNullRule implements Rule{
private static final String MATRIX_NULL = "The input matrix is null";
 @Override
 public void validate( Long[][] inputMatrix ) throwsHungarianException
 {
 if (inputMatrix == null) {
 throw new HungarianException(MATRIX_NULL);
 }
}

Doing the above changes would result in this constructor for the HungarianProblem:

/**
 * Create the hungarian problem.
 * @param inputMatrix The input matrix. It has to be rectangular, but not necessarily square. null entries are allowed to represent 'impossible' entries. The first dimension is the from/agent, and the second dimension is the to/task.
 * @param hungarianOperationMode The hungarian operation mode.
 */
public HungarianProblem(Long[][] inputMatrix, HungarianOperationMode hungarianOperationMode) throws HungarianException
{
 List<Rule> rules = new ArrayList<>();
 rules.add( new MatrixNotNullRule () );
 rules.add( new MatrixHasHeightRule () );
 rules.add( new NoNullRowRule () );
 rules.add( new MatrixRectangularRule () );
 for ( Rule rule : rules )
 {
 rule.validate( inputMatrix );
 } 
 //Now you can be sure that everything is fine
 this.heigth = inputMatrix.length(); //the height
 this.width = inputMatrix[0].length(); //each row has the same length according to this SO post: http://stackoverflow.com/questions/21363104/getting-rows-and-columns-count-of-a-2d-array-without-iterating-on-it
 // Create all the arrays.
 // Note that the internal matrixes are square and have the same width and height, which is the maximum of the width and height supplied.
 // The extra rows/columns will be filled with dummy values.
 this.size = Math.max(this.width,this.height);
 this.originalMatrix = new long[this.size][this.size];
 this.currentMatrix = new long[this.size][this.size];
 this.impossibleMatrix = new boolean[this.size][this.size];
 this.cellStates = new CellState[this.size][this.size];
 this.coveredColumns = new boolean[this.size];
 this.coveredRows = new boolean[this.size];
 this.path = new Point[(this.size*4)+1];
 long maximumValue = Long.MIN_VALUE;
 long minimumValue = Long.MAX_VALUE;
 // A maximum mode problem can be turned into a minimum mode problem just by negating all the values in the matrix. A lower step will then make sure all values are positive.
 long valueModifier = hungarianOperationMode == HungarianOperationMode.MAXIMIZE ? -1 : 1;
 // Populate the original and current amtrix
 for (int row=0; row<this.size; row++) {
 for (int column=0; column<this.size; column++) {
 Long value;
 if (row < this.height && column < this.width) {
 value = inputMatrix[row][column];
 } else {
 value = null;
 }
 this.cellStates[row][column] = CellState.NORMAL;
 if (value == null) {
 this.impossibleMatrix[row][column] = true;
 } else {
 this.originalMatrix[row][column] = value;
 value = Math.multiplyExact(value, valueModifier);
 this.currentMatrix[row][column] = value;
 if (value > maximumValue) {
 maximumValue = value;
 }
 if (value < minimumValue) {
 minimumValue = value;
 }
 }
 }
 }
 // The convention for the hungarian algorithm is to use the maximum value for dummy values.
 // However, in the case where may be missing impossible values in the matrix, this is no longer enough, as it might be possible that the maximum valued entry needs to be used.
 // For that valid choice to happen, instead of one of the dummy values being chosen instead, there needs to be a difference between valid maximum values and invalid values.
 // Then, to make sure the matrix is fully positive, add one the minimum value.
 // Use this newly calculated value and fill in the relevant matrix entries.
 long missingValueCurrent = Math.addExact(maximumValue, 1);
 long missingValueOriginal = Math.multiplyExact(missingValueCurrent, valueModifier);
 for (int row=0; row<this.size; row++) {
 for (int column=0; column<this.size; column++) {
 if (this.impossibleMatrix[row][column]) {
 this.originalMatrix[row][column] = missingValueOriginal;
 this.currentMatrix[row][column] = missingValueCurrent;
 }
 this.currentMatrix[row][column] = Math.subtractExact(this.currentMatrix[row][column], minimumValue);
 }
 }
}

So far, so good. Next: extract your initialization from the constructor:

/** Create all the arrays.
* Note that the internal matrixes are square and have the same width and height, which is the maximum of the width and height supplied.
* The extra rows/columns will be filled with dummy values.
* @param size the size of the matrixes
*/
private void init ( int size ) 
{ 
 this.originalMatrix = new long[size ][size ];
 this.currentMatrix = new long[size ][size ];
 this.impossibleMatrix = new boolean[size ][size ] ;
 this.cellStates = new CellState[size ][size ];
 this.coveredColumns = new boolean[size ];
 this.coveredRows = new boolean[size ];
 this.path = new Point[(size * 4)+1];
}

... and call it in your constructor:

/**
 * Create the hungarian problem.
 * @param inputMatrix The input matrix. It has to be rectangular, but not necessarily square. null entries are allowed to represent 'impossible' entries. The first dimension is the from/agent, and the second dimension is the to/task.
 * @param hungarianOperationMode The hungarian operation mode.
 */
public HungarianProblem(Long[][] inputMatrix, HungarianOperationMode hungarianOperationMode) throws HungarianException
{
 ...
 this.init( Math.max( this.height, this.width ) );
 ...
}

Extract some more functionalities into functions, just the same way I did with the init () method and your code will be a lot easier to understand.


Next: extend your HungarianOperationMode to have a private int modifier

/**
 * The type of hungarian operation that should be ran.
 * @author scott.dennison
 */
public enum HungarianOperationMode {
 /**
 * Find the minimum valued combination possible.
 */
 MINIMIZE( -1l ),
 /**
 * Find the maximum valued combination possible.
 */
 MAXIMIZE( 1l );
 private long modifier;
 private HungarianOperationMode ( long modifier ) 
 {
 this.modifier = modifier;
 }
 public long getModifier () 
 {
 return this.modifier;
 }
}

so you dont need to write

// A maximum mode problem can be turned into a minimum mode problem just by negating all the values in the matrix. A lower step will then make sure all values are positive.
long valueModifier = hungarianOperationMode == ungarianOperationMode.MAXIMIZE ? -1 : 1;

but can write

hungarianOperationMode.getModifier();

instead, whenever needed.

The new constructor would look something like this:

/**
 * Create the hungarian problem.
 * @param inputMatrix The input matrix. It has to be rectangular, but not necessarily square. null entries are allowed to represent 'impossible' entries. The first dimension is the from/agent, and the second dimension is the to/task.
 * @param hungarianOperationMode The hungarian operation mode.
 */
public HungarianProblem(Long[][] inputMatrix, HungarianOperationMode hungarianOperationMode) throws HungarianException
{
 List<Rule> rules = new ArrayList<>();
 rules.add( new MatrixNotNullRule () );
 rules.add( new MatrixHasHeightRule () );
 rules.add( new NoNullRowRule () );
 rules.add( new MatrixRectangularRule () );
 for ( Rule rule : rules )
 {
 rule.validate( inputMatrix );
 } 
 //Now you can be sure that everything is fine
 this.heigth = inputMatrix.length(); //the height
 this.width = inputMatrix[0].length(); //each row has the same length according to this SO post: http://stackoverflow.com/questions/21363104/getting-rows-and-columns-count-of-a-2d-array-without-iterating-on-it
 this.init();
 // A maximum mode problem can be turned into a minimum mode problem just by negating all the values in the matrix. A lower step will then make sure all values are positive.
 long valueModifier = hungarianOperationMode.getModifier();
 // Populate the original and current amtrix
 for (int row=0; row<this.size; row++) {
 for (int column=0; column<this.size; column++) {
 Long value;
 if (row < this.height && column < this.width) {
 value = inputMatrix[row][column];
 } else {
 value = null;
 }
 this.cellStates[row][column] = CellState.NORMAL;
 if (value == null) {
 this.impossibleMatrix[row][column] = true;
 } else {
 this.originalMatrix[row][column] = value;
 value = Math.multiplyExact(value, valueModifier);
 this.currentMatrix[row][column] = value;
 if (value > maximumValue) {
 maximumValue = value;
 }
 if (value < minimumValue) {
 minimumValue = value;
 }
 }
 }
 }
 // The convention for the hungarian algorithm is to use the maximum value for dummy values.
 // However, in the case where may be missing impossible values in the matrix, this is no longer enough, as it might be possible that the maximum valued entry needs to be used.
 // For that valid choice to happen, instead of one of the dummy values being chosen instead, there needs to be a difference between valid maximum values and invalid values.
 // Then, to make sure the matrix is fully positive, add one the minimum value.
 // Use this newly calculated value and fill in the relevant matrix entries.
 long missingValueCurrent = Math.addExact(maximumValue, 1);
 long missingValueOriginal = Math.multiplyExact(missingValueCurrent, valueModifier);
 for (int row=0; row<this.size; row++) {
 for (int column=0; column<this.size; column++) {
 if (this.impossibleMatrix[row][column]) {
 this.originalMatrix[row][column] = missingValueOriginal;
 this.currentMatrix[row][column] = missingValueCurrent;
 }
 this.currentMatrix[row][column] = Math.subtractExact(this.currentMatrix[row][column], minimumValue);
 }
 }
}

Maybe someone will be able to tell you something about the edge cases then.

Edit:

Of course these tips do apply to all of your code. Extracting code into functions improves your general code quality and should be applied whereever possible.

default

AltStyle によって変換されたページ (->オリジナル) /