Skip to content

Commit

Permalink
fix: fix that EffectiveModuleSpecification.simplify() did not remove
Browse files Browse the repository at this point in the history
redundant clauses
  • Loading branch information
Yogu committed Aug 21, 2024
1 parent 67f5ce4 commit 4747011
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 10 deletions.
70 changes: 70 additions & 0 deletions spec/model/implementation/effective-module-specification.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
EffectiveModuleSpecification,
EffectiveModuleSpecificationClause,
} from '../../../src/model/implementation/modules/effective-module-specification';
import { expect } from 'chai';

describe('EffectiveModuleSpecification', () => {
describe('simplify()', () => {
it('removes duplicate simple clauses', () => {
const input = new EffectiveModuleSpecification({
orCombinedClauses: [
new EffectiveModuleSpecificationClause({ andCombinedModules: ['a'] }),
new EffectiveModuleSpecificationClause({ andCombinedModules: ['b'] }),
new EffectiveModuleSpecificationClause({ andCombinedModules: ['a'] }),
],
});
const output = input.simplify();
expect(output.toString()).to.equal('a, b');
});

it('removes duplicate multi-module clauses', () => {
const input = new EffectiveModuleSpecification({
orCombinedClauses: [
new EffectiveModuleSpecificationClause({ andCombinedModules: ['a', 'c'] }),
new EffectiveModuleSpecificationClause({ andCombinedModules: ['b'] }),
new EffectiveModuleSpecificationClause({ andCombinedModules: ['a', 'c'] }),
],
});
const output = input.simplify();
expect(output.toString()).to.equal('b, a && c');
});

it('removes redundant clauses', () => {
const input = new EffectiveModuleSpecification({
orCombinedClauses: [
new EffectiveModuleSpecificationClause({ andCombinedModules: ['a', 'c'] }),
new EffectiveModuleSpecificationClause({ andCombinedModules: ['b'] }),
new EffectiveModuleSpecificationClause({ andCombinedModules: ['a', 'c', 'd'] }),
],
});
const output = input.simplify();
expect(output.toString()).to.equal('b, a && c');
});

it('works with a more complicated case', () => {
const input = new EffectiveModuleSpecification({
orCombinedClauses: [
new EffectiveModuleSpecificationClause({ andCombinedModules: ['shipping'] }),
new EffectiveModuleSpecificationClause({
andCombinedModules: ['shipping', 'dangerous_goods'],
}),
new EffectiveModuleSpecificationClause({
andCombinedModules: ['tms', 'dangerous_goods'],
}),
new EffectiveModuleSpecificationClause({
andCombinedModules: ['shipping', 'dangerous_goods', 'extra1'],
}),
new EffectiveModuleSpecificationClause({
andCombinedModules: ['tms', 'dangerous_goods', 'extra1'],
}),
new EffectiveModuleSpecificationClause({
andCombinedModules: ['extra1'],
}),
],
});
const output = input.simplify();
expect(output.toString()).to.equal('extra1, shipping, dangerous_goods && tms');
});
});
});
39 changes: 29 additions & 10 deletions src/model/implementation/modules/effective-module-specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,33 @@ export class EffectiveModuleSpecification {
}

/**
* Sorts the clauses by name and removes duplicates
* Sorts the clauses by name and removes duplicates and redundant clauses (e.g. ["a", "a && b"] is simplified to ["a"])
*/
simplify(): EffectiveModuleSpecification {
const map = new Map(
this.orCombinedClauses.map((clause) => {
const normalized = clause.normalize();
return [normalized.toString(), normalized];
}),
);
const keys = Array.from(map.keys()).sort();
const normalizedClauses = keys.map((k) => map.get(k)!);
return new EffectiveModuleSpecification({ orCombinedClauses: normalizedClauses });
// sort by length so we prefer shorter clauses
// sort by toString() secondarily to get a normalized representation
const clausesSortedByLength = this.orCombinedClauses
.map((c) => c.normalize())
.sort((a, b) => a.toString().localeCompare(b.toString()))
.sort((a, b) => a.andCombinedModules.length - b.andCombinedModules.length);
const nonRedundantClauses: EffectiveModuleSpecificationClause[] = [];
// this has quadratic runtime w.r.t. number atoms, but we don't expect huge module specifications
for (const clause of clausesSortedByLength) {
const modulesInClause = new Set(clause.andCombinedModules);
let clauseIsRedundant = false;
for (const existingClause of nonRedundantClauses) {
if (existingClause.andCombinedModules.every((m) => modulesInClause.has(m))) {
// existingClause implies clause
clauseIsRedundant = true;
break;
}
}
if (!clauseIsRedundant) {
nonRedundantClauses.push(clause);
}
}

return new EffectiveModuleSpecification({ orCombinedClauses: nonRedundantClauses });
}

orCombineWith(other: EffectiveModuleSpecification) {
Expand Down Expand Up @@ -68,6 +83,10 @@ export class EffectiveModuleSpecification {
});
return combined.simplify();
}

toString() {
return this.orCombinedClauses.map((c) => c.toString()).join(', ');
}
}

export class EffectiveModuleSpecificationClause {
Expand Down

0 comments on commit 4747011

Please sign in to comment.