Skip to content

Commit

Permalink
Merge pull request #60 from ducktordanny/feat/second-phase-api
Browse files Browse the repository at this point in the history
Feat: Second Phase of transportation problem - API side
  • Loading branch information
ducktordanny authored Oct 1, 2022
2 parents 5b6e508 + ccb1888 commit 7f4f142
Show file tree
Hide file tree
Showing 8 changed files with 499 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
secondPhaseFirstResultMock,
secondPhaseSecondResultMock,
vogelKordaFirstResultMock,
vogelKordaSecondResultMock,
} from '@opres/shared/data/mocks';
import {last} from 'lodash';

import {SecondPhaseService} from './second-phase.service';

describe('SecondPhaseService', () => {
let service: SecondPhaseService;

beforeEach(() => {
service = new SecondPhaseService();
});

it('should be created', () =>
expect(service).toBeInstanceOf(SecondPhaseService));

it("should calculate a transport problem's second phase part 1", () => {
const firstPhaseResult = last(vogelKordaFirstResultMock)?.transportation;
const result = service.calculate(firstPhaseResult, 'explanations');
expect(result).toEqual(secondPhaseFirstResultMock);
});

it("should calculate a transport problem's second phase part 2", () => {
const firstPhaseResult = last(vogelKordaSecondResultMock)?.transportation;
const result = service.calculate(firstPhaseResult, 'explanations');
expect(result).toEqual(secondPhaseSecondResultMock);
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,190 @@
import {Injectable} from '@nestjs/common';

import {
AuxiliaryVariables,
CalculationMode,
SecondPhaseStep,
SelectedCell,
TransportTable,
} from '@opres/shared/types';
import {
cloneDeep,
findIndex,
first,
forEach,
last,
map,
minBy,
some,
} from 'lodash';

@Injectable()
export class SecondPhaseService {
public calculate(
transportTable: TransportTable,
mode: CalculationMode,
): Array<SecondPhaseStep> {
// todo wip
return [];
const process: Array<SecondPhaseStep> = [];
let nextBase: SelectedCell = {x: 0, y: 0};
do {
const variables = this.getAuxiliaryVariable(transportTable);
nextBase = this.getNextNewBase(transportTable, variables);
if (nextBase?.value !== undefined) {
const circle = this.getCircle(transportTable, nextBase);
process.push({
transportation: cloneDeep(transportTable),
auxiliaryVariables: variables,
nextBase: cloneDeep(nextBase),
circle,
});
this.reduceTransportTable(transportTable, cloneDeep(circle));
continue;
}
process.push({
transportation: cloneDeep(transportTable),
auxiliaryVariables: variables,
});
} while (nextBase?.value !== undefined);
return process;
}

private getAuxiliaryVariable(
transportTable: TransportTable,
): AuxiliaryVariables {
const variables: AuxiliaryVariables = {
x: map(transportTable[0], () => null),
y: map(transportTable, () => null),
};
const initialRow = findIndex(transportTable, (row) =>
some(row, (cell) => !!cell.transported),
);
variables.y[initialRow] = 0;

const containsNull = (variable) => variable === null;
while (variables.x.some(containsNull) || variables.y.some(containsNull)) {
forEach(transportTable, (row, rowIndex) =>
forEach(row, (cell, columnIndex) => {
const currentXVariable = variables.x[columnIndex];
const currentYVariable = variables.y[rowIndex];

if (cell.transported === undefined) return;
if (currentXVariable === null && currentYVariable === null) return;
if (currentXVariable !== null && currentYVariable !== null) return;

if (currentYVariable === null)
variables.y[rowIndex] = cell.cost - variables.x[columnIndex];
else if (currentXVariable === null)
variables.x[columnIndex] = cell.cost - variables.y[rowIndex];
}),
);
}
return variables;
}

private getNextNewBase(
transportTable: TransportTable,
variables: AuxiliaryVariables,
): SelectedCell {
let newBase: SelectedCell = {value: undefined, x: 0, y: 0};
forEach(transportTable, (row, rowIndex) =>
forEach(row, (cell, columnIndex) => {
if (cell.transported !== undefined)
return (cell.reducedCost = undefined);
const reducedCost =
cell.cost - (variables.x[columnIndex] + variables.y[rowIndex]);
transportTable[rowIndex][columnIndex].reducedCost = reducedCost;
if (
(newBase?.value === undefined && reducedCost < 0) ||
newBase?.value > reducedCost
)
newBase = {value: reducedCost, x: +columnIndex, y: rowIndex};
}),
);
return newBase;
}

private getCircle(
transportTable: TransportTable,
nextBase: SelectedCell,
): Array<SelectedCell> {
let baseCells: Array<SelectedCell> = this.getBaseCellPositions(
transportTable,
nextBase,
);
let removedCells = Infinity;
while (removedCells > 0) {
const filteredBaseCells = baseCells.filter((value, _, array) => {
const xIndexes = array.filter((v) => v.x === value.x).length;
const yIndexes = array.filter((v) => v.y === value.y).length;
return xIndexes > 1 && yIndexes > 1;
});
removedCells = baseCells.length - filteredBaseCells.length;
baseCells = filteredBaseCells;
}
return this.sortCircleCells(baseCells);
}

private getMaxReducableValueFromCircle(circle: Array<SelectedCell>): number {
return minBy(
circle?.filter((cell, index) => cell?.value !== -1 && index % 2 !== 0),
'value',
)?.value;
}

private sortCircleCells(circle: Array<SelectedCell>): Array<SelectedCell> {
const comparingIndex = (isX: boolean) => (isX ? 'x' : 'y');
let isX = true;
const sortedCircle = [circle[0]];
for (let i = 1; i < circle.length; i++) {
const lastSortedCell = last(sortedCircle);
const baseCell = circle?.find(
(value) =>
value?.[comparingIndex(isX)] ===
lastSortedCell?.[comparingIndex(isX)] &&
value?.[comparingIndex(!isX)] !==
lastSortedCell?.[comparingIndex(!isX)],
);
sortedCircle.push(baseCell);
isX = !isX;
}
return sortedCircle;
}

private reduceTransportTable(
transportTable: TransportTable,
circle: Array<SelectedCell>,
): void {
const maxReducableValue = this.getMaxReducableValueFromCircle(circle);
first(circle).value = maxReducableValue;
for (let i = 1; i < circle.length; i++) {
if (!circle[i]) continue;
if (i % 2) circle[i].value -= maxReducableValue;
else circle[i].value += maxReducableValue;
}
forEach(circle, (cell) => {
transportTable[cell?.y][cell?.x].transported =
cell?.value === 0 ? undefined : cell?.value;
});
}

private getBaseCellPositions(
transportTable: TransportTable,
nextBase?: SelectedCell,
): Array<SelectedCell> {
const baseCellPositions: Array<SelectedCell> = [];
if (nextBase) baseCellPositions.push({...nextBase, value: -1});

forEach(transportTable, (row, rowIndex) =>
forEach(row, (cell, columnIndex) => {
if (cell.transported !== undefined)
baseCellPositions.push({
x: +columnIndex,
y: rowIndex,
value: cell.transported,
});
}),
);

return baseCellPositions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {TransportTable} from '@opres/shared/types';
import {countBy} from 'lodash';

import {getColumnUtil} from './get-column.util';

export function getBaseCellsInARow(
transportTable: TransportTable,
rowIndex: number,
): number {
return countBy(
transportTable[rowIndex],
(cell) => cell.transported !== undefined,
).true;
}

export function getBaseCellsInColumn(
transportTable: TransportTable,
columnIndex: number,
): number {
const column = getColumnUtil(transportTable, columnIndex);
return countBy(column, (cell) => cell.transported !== undefined).true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Cell, TransportTable} from '@opres/shared/types';

export function getColumnUtil(
table: TransportTable,
columnIndex: number,
): Array<Cell> {
return table.map((row) => row[columnIndex]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class TransportProblemService {
return this.getEpsilonResult(
last(steps)?.transportation || [],
mode === 'explanations',
).pipe(map((epsilon) => ({steps, epsilon} as FirstPhaseResult)));
).pipe(map((epsilon) => ({steps, epsilon} as SecondPhaseResult)));
}),
);
}
Expand Down
1 change: 1 addition & 0 deletions libs/shared/data/mocks/src/lib/transport-problem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './transport-problem.mock';
export * from './north-west-result.mock';
export * from './table-minimum-result.mock';
export * from './vogel-korda-result.mock';
export * from './second-phase-result.mock';
Loading

0 comments on commit 7f4f142

Please sign in to comment.