diff --git a/package.json b/package.json index 8e31041..56bfc13 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@aureooms/js-maximum-matching", "description": "Maximum matching algorithms for JavaScript", - "version": "1.0.7", + "version": "1.0.8", "author": "aureooms", "ava": { "files": [ diff --git a/src/core/blossom/blossom.js b/src/core/blossom/blossom.js index 96f90f3..35643da 100644 --- a/src/core/blossom/blossom.js +++ b/src/core/blossom/blossom.js @@ -8,6 +8,7 @@ import statistics from './statistics'; import endpoints from './endpoints'; import neighbours from './neighbours'; import blossomLeaves from './blossomLeaves'; +import blossomEdges from './blossomEdges'; // Adapted from http://jorisvr.nl/maximummatching.html // All credit for the implementation goes to Joris van Rantwijk [http://jorisvr.nl]. @@ -55,7 +56,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { * @return {Array} */ - const maxWeightMatching = function (edges, maxCardinality = false) { + const maxWeightMatching = (edges, maxCardinality = false) => { // Vertices are numbered 0 .. (nvertex-1). // Non-trivial blossoms are numbered nvertex .. (2*nvertex-1) // @@ -184,10 +185,11 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Assign label t to the top-level blossom containing vertex w // and record the fact that w was reached through the edge with // remote endpoint p. - const assignLabel = function (w, t, p) { + const assignLabel = (w, t, p) => { console.debug('DEBUG: assignLabel(' + w + ',' + t + ',' + p + ')'); const b = inblossom[w]; assert(label[w] === 0 && label[b] === 0); + assert(t === 1 || t === 2); label[w] = t; label[b] = t; labelend[w] = p; @@ -201,7 +203,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } console.debug('DEBUG: PUSH ' + queue); - } else if (t === 2) { + } else { // B became a T-vertex/blossom; assign label S to its mate. // (If b is a non-trivial blossom, its base is the only vertex // with an external mate.) @@ -213,16 +215,14 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Trace back from vertices v and w to discover either a new blossom // or an augmenting path. Return the base vertex of the new blossom or -1. - const scanBlossom = function (v, w) { + const scanBlossom = (v, w) => { console.debug('DEBUG: scanBlossom(' + v + ',' + w + ')'); // Trace back from v and w, placing breadcrumbs as we go. - let b; - let i; const path = []; let base = -1; while (v !== -1 || w !== -1) { // Look for a breadcrumb in v's blossom or put a new breadcrumb. - b = inblossom[v]; + let b = inblossom[v]; if (label[b] & 4) { base = blossombase[b]; break; @@ -254,11 +254,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } // Remove breadcrumbs. - i = path.length; - while (i--) { - b = path[i]; - label[b] = 1; - } + for (const b of path) label[b] = 1; // Return base vertex, if we found one. return base; @@ -267,11 +263,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Construct a new blossom with given base, containing edge k which // connects a pair of S vertices. Label the new blossom as S; set its dual // variable to zero; relabel its T-vertices to S and add them to the queue. - const addBlossom = function (base, k) { - let i; - let j; - let nblist; - let nblists; + const addBlossom = (base, k) => { let v = edges[k][0]; let w = edges[k][1]; const bb = inblossom[base]; @@ -359,51 +351,25 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { const length_ = path.length; for (let z = 0; z < length_; ++z) { - bv = path[z]; - - if (blossombestedges[bv] === null) { + const bv = path[z]; + // Walk this subblossom's least-slack edges. + let nblist = blossombestedges[bv]; + if (nblist === null) { // This subblossom does not have a list of least-slack edges; // get the information from the vertices. - nblists = []; - for (const v of blossomLeaves(nvertex, blossomchilds, bv)) { - j = neighbend[v].length; - const temporary_ = new Array(j); - while (j--) { - const p = neighbend[v][j]; - temporary_[j] = Math.floor(p / 2); - } - - nblists.push(temporary_); - } - } else { - // Walk this subblossom's least-slack edges. - nblists = [blossombestedges[bv]]; + nblist = blossomEdges(nvertex, blossomchilds, neighbend, bv); } - for (let x = 0, m = nblists.length; x < m; ++x) { - nblist = nblists[x]; - - for (let y = 0, n = nblist.length; y < n; ++y) { - const k = nblist[y]; + for (const k of nblist) { + const [i, j] = edges[k]; + const bj = inblossom[j] === b ? inblossom[i] : inblossom[j]; - let i = edges[k][0]; - let j = edges[k][1]; - - if (inblossom[j] === b) { - const temporary_ = i; - i = j; - j = temporary_; - } - - const bj = inblossom[j]; - - if ( - bj !== b && - label[bj] === 1 && - (bestedgeto[bj] === -1 || slack(k) < slack(bestedgeto[bj])) - ) { - bestedgeto[bj] = k; - } + if ( + bj !== b && + label[bj] === 1 && + (bestedgeto[bj] === -1 || slack(k) < slack(bestedgeto[bj])) + ) { + bestedgeto[bj] = k; } } @@ -414,7 +380,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { blossombestedges[b] = []; const length_2 = bestedgeto.length; - for (i = 0; i < length_2; ++i) { + for (let i = 0; i < length_2; ++i) { k = bestedgeto[i]; if (k !== -1) blossombestedges[b].push(k); } @@ -424,7 +390,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { const length_3 = blossombestedges[b].length; if (length_3 > 0) { bestedge[b] = blossombestedges[b][0]; - for (i = 1; i < length_3; ++i) { + for (let i = 1; i < length_3; ++i) { k = blossombestedges[b][i]; if (slack(k) < slack(bestedge[b])) { bestedge[b] = k; @@ -436,24 +402,13 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { }; // Expand the given top-level blossom. - const expandBlossom = function (b, endstage) { + const expandBlossom = (b, endstage) => { console.debug( 'DEBUG: expandBlossom(' + b + ',' + endstage + ') ' + blossomchilds[b] ); // Convert sub-blossoms into top-level blossoms. - let i; - let j; - let s; - let p; - let entrychild; - let jstep; - let endptrick; - let bv; - let stop; - let base; - - for (i = 0; i < blossomchilds[b].length; ++i) { - s = blossomchilds[b][i]; + for (let i = 0; i < blossomchilds[b].length; ++i) { + const s = blossomchilds[b][i]; blossomparent[s] = -1; if (s < nvertex) inblossom[s] = s; @@ -476,9 +431,13 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Figure out through which sub-blossom the expanding blossom // obtained its label initially. assert(labelend[b] >= 0); - entrychild = inblossom[endpoint[labelend[b] ^ 1]]; + const entrychild = inblossom[endpoint[labelend[b] ^ 1]]; // Decide in which direction we will go round the blossom. - j = blossomchilds[b].indexOf(entrychild); + let j = blossomchilds[b].indexOf(entrychild); + let jstep; + let endptrick; + let stop; + let base; if (j & 1) { // Start index is odd; go forward. jstep = 1; @@ -494,7 +453,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } // Move along the blossom until we get to the base. - p = labelend[b]; + let p = labelend[b]; while (j !== stop) { // Relabel the T-sub-blossom. label[endpoint[p ^ 1]] = 0; @@ -511,7 +470,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Relabel the base T-sub-blossom WITHOUT stepping through to // its mate (so don't call assignLabel). - bv = blossomchilds[b][0]; + let bv = blossomchilds[b][0]; label[endpoint[p ^ 1]] = 2; label[bv] = 2; labelend[endpoint[p ^ 1]] = p; @@ -560,17 +519,16 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Swap matched/unmatched edges over an alternating path through blossom b // between vertex v and the base vertex. Keep blossom bookkeeping consistent. - const augmentBlossom = function (b, v) { + const augmentBlossom = (b, v) => { console.debug('DEBUG: augmentBlossom(' + b + ',' + v + ')'); // Bubble up through the blossom tree from vertex v to an immediate // sub-blossom of b. let j; - let t; let jstep; let endptrick; let stop; let p; - t = v; + let t = v; while (blossomparent[t] !== b) t = blossomparent[t]; // Recursively deal with the first sub-blossom. if (t >= nvertex) augmentBlossom(t, v); @@ -625,7 +583,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Swap matched/unmatched edges over an alternating path between two // single vertices. The augmenting path runs through edge k, which // connects a pair of S vertices. - const augmentMatching = function (k) { + const augmentMatching = (k) => { const v = edges[k][0]; const w = edges[k][1]; @@ -678,11 +636,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } }; - let b; let d; - let t; - let v; - let augmented; let kslack; let base; let deltatype; @@ -691,7 +645,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { let deltablossom; // Main loop: continue until no further improvement is possible. - for (t = 0; t < nvertex; ++t) { + for (let t = 0; t < nvertex; ++t) { // Each iteration of this loop is a "stage". // A stage finds an augmenting path and uses that to improve // the matching. @@ -712,12 +666,12 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { queue = []; // Label single blossoms/vertices with S and put them in the queue. - for (v = 0; v < nvertex; ++v) { + for (let v = 0; v < nvertex; ++v) { if (mate[v] === -1 && label[inblossom[v]] === 0) assignLabel(v, 1, -1); } // Loop until we succeed in augmenting the matching. - augmented = 0; + let augmented = false; // eslint-disable-next-line no-constant-condition while (true) { // Each iteration of this loop is a "substage". @@ -732,7 +686,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // through an alternating path have got a label. while (queue.length && !augmented) { // Take an S vertex from the queue. - v = queue.pop(); + const v = queue.pop(); console.debug('DEBUG: POP v=' + v); assert(label[inblossom[v]] === 1); @@ -774,7 +728,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Found an augmenting path; augment the // matching and end this stage. augmentMatching(k); - augmented = 1; + augmented = true; break; } } else if (label[w] === 0) { @@ -789,7 +743,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } else if (label[inblossom[w]] === 1) { // Keep track of the least-slack non-allowable edge to // a different S-blossom. - b = inblossom[v]; + const b = inblossom[v]; if (bestedge[b] === -1 || kslack < slack(bestedge[b])) bestedge[b] = k; } else if (label[w] === 0) { @@ -895,7 +849,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } // Update dual variables according to delta. - for (v = 0; v < nvertex; ++v) { + for (let v = 0; v < nvertex; ++v) { if (label[inblossom[v]] === 1) { // S-vertex: 2*u = 2*u - 2*delta dualvar[v] -= delta; @@ -905,7 +859,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { } } - for (b = nvertex; b < 2 * nvertex; ++b) { + for (let b = nvertex; b < 2 * nvertex; ++b) { if (blossombase[b] >= 0 && blossomparent[b] === -1) { if (label[b] === 1) { // Top-level S-blossom: z = z + 2*delta @@ -919,6 +873,12 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Take action at the point where minimum delta occurred. console.debug('DEBUG: delta' + deltatype + '=' + delta); + assert( + deltatype === 1 || + deltatype === 2 || + deltatype === 3 || + deltatype === 4 + ); if (deltatype === 1) { // No further improvement possible; optimum reached. break; @@ -926,13 +886,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { // Use the least-slack edge to continue the search. allowedge[deltaedge] = true; let i = edges[deltaedge][0]; - let j = edges[deltaedge][1]; - if (label[inblossom[i]] === 0) { - const temporary = i; - i = j; - j = temporary; - } - + if (label[inblossom[i]] === 0) i = edges[deltaedge][1]; assert(label[inblossom[i]] === 1); queue.push(i); } else if (deltatype === 3) { @@ -941,7 +895,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { const i = edges[deltaedge][0]; assert(label[inblossom[i]] === 1); queue.push(i); - } else if (deltatype === 4) { + } else { // Expand the least-z blossom. expandBlossom(deltablossom, false); } @@ -953,7 +907,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { if (!augmented) break; // End of a stage; expand all S-blossoms which have dualvar = 0. - for (b = nvertex; b < 2 * nvertex; ++b) { + for (let b = nvertex; b < 2 * nvertex; ++b) { if ( blossomparent[b] === -1 && blossombase[b] >= 0 && @@ -981,13 +935,13 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) { }); // Transform mate[] such that mate[v] is the vertex to which v is paired. - for (v = 0; v < nvertex; ++v) { + for (let v = 0; v < nvertex; ++v) { if (mate[v] >= 0) { mate[v] = endpoint[mate[v]]; } } - for (v = 0; v < nvertex; ++v) { + for (let v = 0; v < nvertex; ++v) { assert(mate[v] === -1 || mate[mate[v]] === v); } diff --git a/src/core/blossom/blossomEdges.js b/src/core/blossom/blossomEdges.js new file mode 100644 index 0000000..2aeb80c --- /dev/null +++ b/src/core/blossom/blossomEdges.js @@ -0,0 +1,7 @@ +import blossomLeaves from './blossomLeaves'; + +export default function* blossomEdges(nvertex, blossomchilds, neighbend, bv) { + for (const v of blossomLeaves(nvertex, blossomchilds, bv)) { + for (const p of neighbend[v]) yield Math.floor(p / 2); + } +}