├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG ├── LICENSE ├── README.md ├── bower.json ├── example.html ├── munkres.js ├── package.json └── test └── test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js ident 2 | *.txt ident 3 | README ident 4 | CHANGELOG ident 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | coverage 4 | .nyc_output 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .gitattributes 3 | node_modules 4 | bower_components 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "4" 6 | - "6" 7 | - "8" 8 | script: 9 | - npm test 10 | after_success: 11 | - nyc report --reporter=text-lcov | coveralls 12 | sudo: false 13 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 1.1.0 (6 November, 2014) 2 | - Port to JS 3 | 4 | Original Python Changelog: 5 | 6 | --------------------------------------------------------------------------- 7 | 8 | 1.0.5.3 (2 August, 2009) 9 | 10 | - Fixed documentation of print_matrix() in module docs. 11 | 12 | --------------------------------------------------------------------------- 13 | 1.0.5.2 (30 June, 2008): 14 | 15 | - Incorporated some suggestions optimizations from Mark Summerfield 16 | (mark /at/ qtrac.eu) 17 | - Munkres.make_cost_matrix() is now deprecated, in favor of a module-level 18 | function. 19 | - The module now provides a print_matrix() convenience function. 20 | - Fixed some bugs related to the padding of non-square matrics. 21 | 22 | --------------------------------------------------------------------------- 23 | 1.0.5.1 (26 June, 2008): 24 | 25 | - Some minor doc changes. 26 | 27 | --------------------------------------------------------------------------- 28 | 1.0.5 (26 June, 2008): 29 | 30 | - Now handles non-square cost matrices by padding them with zeros. 31 | - Converted Epydocs to use reStructuredText. 32 | 33 | --------------------------------------------------------------------------- 34 | 1.0.4 (13 June, 2008) 35 | 36 | - Minor bug fix in main (tester) program in munkres.py 37 | 38 | --------------------------------------------------------------------------- 39 | 1.0.3 (16 March, 2008) 40 | 41 | - Minor change to prevent shadowing of built-in min() function. Thanks to 42 | Nelson Castillo (nelson /at/ emqbit.com) for pointing it out. 43 | 44 | --------------------------------------------------------------------------- 45 | 1.0.2 (21 February, 2008) 46 | 47 | - Fixed an overindexing bug reported by Chris Willmore (willmc rpi.edu) 48 | 49 | --------------------------------------------------------------------------- 50 | 1.0.1 (16 February, 2008) 51 | 52 | - Documentation now processed by Epydoc. 53 | 54 | --------------------------------------------------------------------------- 55 | 1.0 (January, 2008) 56 | 57 | - Initial release. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008-2016 Brian M. Clapper 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Munkres implementation for Javascript 2 | --------------------------------- 3 | 4 | ![Bower Version](https://img.shields.io/bower/v/munkres-js.svg?style=flat) 5 | [![NPM Version](https://img.shields.io/npm/v/munkres-js.svg?style=flat)](https://npmjs.org/package/munkres-js) 6 | [![NPM Downloads](https://img.shields.io/npm/dm/munkres-js.svg?style=flat)](https://npmjs.org/package/munkres-js) 7 | [![Build Status](https://travis-ci.org/addaleax/munkres-js.svg?style=flat&branch=master)](https://travis-ci.org/addaleax/munkres-js?branch=master) 8 | [![Coverage Status](https://coveralls.io/repos/addaleax/munkres-js/badge.svg?branch=master)](https://coveralls.io/r/addaleax/munkres-js?branch=master) 9 | [![Dependency Status](https://david-dm.org/addaleax/munkres-js.svg?style=flat)](https://david-dm.org/addaleax/munkres-js) 10 | [![devDependency Status](https://david-dm.org/addaleax/munkres-js/dev-status.svg?style=flat)](https://david-dm.org/addaleax/munkres-js#info=devDependencies) 11 | 12 | ## Introduction 13 | 14 | The Munkres module provides an O(n³) implementation of the Munkres algorithm 15 | (also called the [Hungarian algorithm][] or the Kuhn-Munkres algorithm). 16 | The algorithm models an assignment problem as an N×M cost matrix, where 17 | each element represents the cost of assigning the ith worker to the jth 18 | job, and it figures out the least-cost solution, choosing a single item 19 | from each row and column in the matrix, such that no row and no column are 20 | used more than once. 21 | 22 | [Hungarian algorithm]: https://en.wikipedia.org/wiki/Hungarian_algorithm 23 | 24 | ## Usage 25 | 26 | ```js 27 | var munkres = require('munkres-js'); 28 | 29 | munkres([ 30 | [400, 150, 400], 31 | [400, 450, 600], 32 | [300, 225, 300] 33 | ]) 34 | // => [ [ 0, 1 ], [ 1, 0 ], [ 2, 2 ] ] 35 | ``` 36 | 37 | Returns the list of matrix indices corresponding to the optimal assignment. 38 | 39 | When used in the browser, the global `computeMunkres` function is exposed. 40 | 41 | See the docs in munkres.js for more details. 42 | 43 | ## Meta 44 | 45 | This module is a translation of a Python implementation by 46 | [Brian Clapper](https://github.com/bmc/munkres). 47 | 48 | The original implementation is based on 49 | . 50 | 51 | It is available via `bower` and `npm` as `munkres-js`. 52 | 53 | ## Copyright 54 | 55 | © 2014 Anna Henningsen (Conversion to JS) 56 | 57 | © 2008 Brian M. Clapper 58 | 59 | ## License 60 | 61 | Apache License 2.0. See accompanying LICENSE file. 62 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "munkres-js", 3 | "main": "munkres.js", 4 | "version": "1.2.2", 5 | "homepage": "https://github.com/addaleax/munkres-js", 6 | "authors": [ 7 | "sqrt " 8 | ], 9 | "description": "Munkres (aka Hungarian) algorithm for JS", 10 | "moduleType": [ 11 | "globals" 12 | ], 13 | "keywords": [ 14 | "assignment", 15 | "munkres", 16 | "hungarian" 17 | ], 18 | "license": "Apache-2.0 OR BSD-3-Clause", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example for munkres-js 5 | 6 | 60 | 61 | 69 | 70 | 71 |

Example usage of munkres-js

72 | 73 |
74 |

75 | Total Cost: 76 |

77 |

78 | Indices: 79 |

80 | 81 | 82 | -------------------------------------------------------------------------------- /munkres.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Introduction 3 | * ============ 4 | * 5 | * The Munkres module provides an implementation of the Munkres algorithm 6 | * (also called the Hungarian algorithm or the Kuhn-Munkres algorithm), 7 | * useful for solving the Assignment Problem. 8 | * 9 | * Assignment Problem 10 | * ================== 11 | * 12 | * Let C be an n×n-matrix representing the costs of each of n workers 13 | * to perform any of n jobs. The assignment problem is to assign jobs to 14 | * workers in a way that minimizes the total cost. Since each worker can perform 15 | * only one job and each job can be assigned to only one worker the assignments 16 | * represent an independent set of the matrix C. 17 | * 18 | * One way to generate the optimal set is to create all permutations of 19 | * the indices necessary to traverse the matrix so that no row and column 20 | * are used more than once. For instance, given this matrix (expressed in 21 | * Python) 22 | * 23 | * matrix = [[5, 9, 1], 24 | * [10, 3, 2], 25 | * [8, 7, 4]] 26 | * 27 | * You could use this code to generate the traversal indices:: 28 | * 29 | * def permute(a, results): 30 | * if len(a) == 1: 31 | * results.insert(len(results), a) 32 | * 33 | * else: 34 | * for i in range(0, len(a)): 35 | * element = a[i] 36 | * a_copy = [a[j] for j in range(0, len(a)) if j != i] 37 | * subresults = [] 38 | * permute(a_copy, subresults) 39 | * for subresult in subresults: 40 | * result = [element] + subresult 41 | * results.insert(len(results), result) 42 | * 43 | * results = [] 44 | * permute(range(len(matrix)), results) # [0, 1, 2] for a 3x3 matrix 45 | * 46 | * After the call to permute(), the results matrix would look like this:: 47 | * 48 | * [[0, 1, 2], 49 | * [0, 2, 1], 50 | * [1, 0, 2], 51 | * [1, 2, 0], 52 | * [2, 0, 1], 53 | * [2, 1, 0]] 54 | * 55 | * You could then use that index matrix to loop over the original cost matrix 56 | * and calculate the smallest cost of the combinations 57 | * 58 | * n = len(matrix) 59 | * minval = sys.maxsize 60 | * for row in range(n): 61 | * cost = 0 62 | * for col in range(n): 63 | * cost += matrix[row][col] 64 | * minval = min(cost, minval) 65 | * 66 | * print minval 67 | * 68 | * While this approach works fine for small matrices, it does not scale. It 69 | * executes in O(n!) time: Calculating the permutations for an n×x-matrix 70 | * requires n! operations. For a 12×12 matrix, that’s 479,001,600 71 | * traversals. Even if you could manage to perform each traversal in just one 72 | * millisecond, it would still take more than 133 hours to perform the entire 73 | * traversal. A 20×20 matrix would take 2,432,902,008,176,640,000 operations. At 74 | * an optimistic millisecond per operation, that’s more than 77 million years. 75 | * 76 | * The Munkres algorithm runs in O(n³) time, rather than O(n!). This 77 | * package provides an implementation of that algorithm. 78 | * 79 | * This version is based on 80 | * http://csclab.murraystate.edu/~bob.pilgrim/445/munkres.html 81 | * 82 | * This version was originally written for Python by Brian Clapper from the 83 | * algorithm at the above web site (The ``Algorithm::Munkres`` Perl version, 84 | * in CPAN, was clearly adapted from the same web site.) and ported to 85 | * JavaScript by Anna Henningsen (addaleax). 86 | * 87 | * Usage 88 | * ===== 89 | * 90 | * Construct a Munkres object 91 | * 92 | * var m = new Munkres(); 93 | * 94 | * Then use it to compute the lowest cost assignment from a cost matrix. Here’s 95 | * a sample program 96 | * 97 | * var matrix = [[5, 9, 1], 98 | * [10, 3, 2], 99 | * [8, 7, 4]]; 100 | * var m = new Munkres(); 101 | * var indices = m.compute(matrix); 102 | * console.log(format_matrix(matrix), 'Lowest cost through this matrix:'); 103 | * var total = 0; 104 | * for (var i = 0; i < indices.length; ++i) { 105 | * var row = indices[l][0], col = indices[l][1]; 106 | * var value = matrix[row][col]; 107 | * total += value; 108 | * 109 | * console.log('(' + rol + ', ' + col + ') -> ' + value); 110 | * } 111 | * 112 | * console.log('total cost:', total); 113 | * 114 | * Running that program produces:: 115 | * 116 | * Lowest cost through this matrix: 117 | * [5, 9, 1] 118 | * [10, 3, 2] 119 | * [8, 7, 4] 120 | * (0, 0) -> 5 121 | * (1, 1) -> 3 122 | * (2, 2) -> 4 123 | * total cost: 12 124 | * 125 | * The instantiated Munkres object can be used multiple times on different 126 | * matrices. 127 | * 128 | * Non-square Cost Matrices 129 | * ======================== 130 | * 131 | * The Munkres algorithm assumes that the cost matrix is square. However, it's 132 | * possible to use a rectangular matrix if you first pad it with 0 values to make 133 | * it square. This module automatically pads rectangular cost matrices to make 134 | * them square. 135 | * 136 | * Notes: 137 | * 138 | * - The module operates on a *copy* of the caller's matrix, so any padding will 139 | * not be seen by the caller. 140 | * - The cost matrix must be rectangular or square. An irregular matrix will 141 | * *not* work. 142 | * 143 | * Calculating Profit, Rather than Cost 144 | * ==================================== 145 | * 146 | * The cost matrix is just that: A cost matrix. The Munkres algorithm finds 147 | * the combination of elements (one from each row and column) that results in 148 | * the smallest cost. It’s also possible to use the algorithm to maximize 149 | * profit. To do that, however, you have to convert your profit matrix to a 150 | * cost matrix. The simplest way to do that is to subtract all elements from a 151 | * large value. 152 | * 153 | * The ``munkres`` module provides a convenience method for creating a cost 154 | * matrix from a profit matrix, i.e. make_cost_matrix. 155 | * 156 | * References 157 | * ========== 158 | * 159 | * 1. http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html 160 | * 161 | * 2. Harold W. Kuhn. The Hungarian Method for the assignment problem. 162 | * *Naval Research Logistics Quarterly*, 2:83-97, 1955. 163 | * 164 | * 3. Harold W. Kuhn. Variants of the Hungarian method for assignment 165 | * problems. *Naval Research Logistics Quarterly*, 3: 253-258, 1956. 166 | * 167 | * 4. Munkres, J. Algorithms for the Assignment and Transportation Problems. 168 | * *Journal of the Society of Industrial and Applied Mathematics*, 169 | * 5(1):32-38, March, 1957. 170 | * 171 | * 5. https://en.wikipedia.org/wiki/Hungarian_algorithm 172 | * 173 | * Copyright and License 174 | * ===================== 175 | * 176 | * Copyright 2008-2016 Brian M. Clapper 177 | * 178 | * Licensed under the Apache License, Version 2.0 (the "License"); 179 | * you may not use this file except in compliance with the License. 180 | * You may obtain a copy of the License at 181 | * 182 | * http://www.apache.org/licenses/LICENSE-2.0 183 | * 184 | * Unless required by applicable law or agreed to in writing, software 185 | * distributed under the License is distributed on an "AS IS" BASIS, 186 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 187 | * See the License for the specific language governing permissions and 188 | * limitations under the License. 189 | */ 190 | 191 | /** 192 | * A very large numerical value which can be used like an integer 193 | * (i. e., adding integers of similar size does not result in overflow). 194 | */ 195 | var MAX_SIZE = parseInt(Number.MAX_SAFE_INTEGER/2) || ((1 << 26)*(1 << 26)); 196 | 197 | /** 198 | * A default value to pad the cost matrix with if it is not quadratic. 199 | */ 200 | var DEFAULT_PAD_VALUE = 0; 201 | 202 | // --------------------------------------------------------------------------- 203 | // Classes 204 | // --------------------------------------------------------------------------- 205 | 206 | /** 207 | * Calculate the Munkres solution to the classical assignment problem. 208 | * See the module documentation for usage. 209 | * @constructor 210 | */ 211 | function Munkres() { 212 | this.C = null; 213 | 214 | this.row_covered = []; 215 | this.col_covered = []; 216 | this.n = 0; 217 | this.Z0_r = 0; 218 | this.Z0_c = 0; 219 | this.marked = null; 220 | this.path = null; 221 | } 222 | 223 | /** 224 | * Pad a possibly non-square matrix to make it square. 225 | * 226 | * @param {Array} matrix An array of arrays containing the matrix cells 227 | * @param {Number} [pad_value] The value used to pad a rectangular matrix 228 | * 229 | * @return {Array} An array of arrays representing the padded matrix 230 | */ 231 | Munkres.prototype.pad_matrix = function(matrix, pad_value) { 232 | pad_value = pad_value || DEFAULT_PAD_VALUE; 233 | 234 | var max_columns = 0; 235 | var total_rows = matrix.length; 236 | var i; 237 | 238 | for (i = 0; i < total_rows; ++i) 239 | if (matrix[i].length > max_columns) 240 | max_columns = matrix[i].length; 241 | 242 | total_rows = max_columns > total_rows ? max_columns : total_rows; 243 | 244 | var new_matrix = []; 245 | 246 | for (i = 0; i < total_rows; ++i) { 247 | var row = matrix[i] || []; 248 | var new_row = row.slice(); 249 | 250 | // If this row is too short, pad it 251 | while (total_rows > new_row.length) 252 | new_row.push(pad_value); 253 | 254 | new_matrix.push(new_row); 255 | } 256 | 257 | return new_matrix; 258 | }; 259 | 260 | /** 261 | * Compute the indices for the lowest-cost pairings between rows and columns 262 | * in the database. Returns a list of (row, column) tuples that can be used 263 | * to traverse the matrix. 264 | * 265 | * **WARNING**: This code handles square and rectangular matrices. 266 | * It does *not* handle irregular matrices. 267 | * 268 | * @param {Array} cost_matrix The cost matrix. If this cost matrix is not square, 269 | * it will be padded with DEFAULT_PAD_VALUE. Optionally, 270 | * the pad value can be specified via options.padValue. 271 | * This method does *not* modify the caller's matrix. 272 | * It operates on a copy of the matrix. 273 | * @param {Object} [options] Additional options to pass in 274 | * @param {Number} [options.padValue] The value to use to pad a rectangular cost_matrix 275 | * 276 | * @return {Array} An array of ``(row, column)`` arrays that describe the lowest 277 | * cost path through the matrix 278 | */ 279 | Munkres.prototype.compute = function(cost_matrix, options) { 280 | 281 | options = options || {}; 282 | options.padValue = options.padValue || DEFAULT_PAD_VALUE; 283 | 284 | this.C = this.pad_matrix(cost_matrix, options.padValue); 285 | this.n = this.C.length; 286 | this.original_length = cost_matrix.length; 287 | this.original_width = cost_matrix[0].length; 288 | 289 | var nfalseArray = []; /* array of n false values */ 290 | while (nfalseArray.length < this.n) 291 | nfalseArray.push(false); 292 | this.row_covered = nfalseArray.slice(); 293 | this.col_covered = nfalseArray.slice(); 294 | this.Z0_r = 0; 295 | this.Z0_c = 0; 296 | this.path = this.__make_matrix(this.n * 2, 0); 297 | this.marked = this.__make_matrix(this.n, 0); 298 | 299 | var step = 1; 300 | 301 | var steps = { 1 : this.__step1, 302 | 2 : this.__step2, 303 | 3 : this.__step3, 304 | 4 : this.__step4, 305 | 5 : this.__step5, 306 | 6 : this.__step6 }; 307 | 308 | while (true) { 309 | var func = steps[step]; 310 | if (!func) // done 311 | break; 312 | 313 | step = func.apply(this); 314 | } 315 | 316 | var results = []; 317 | for (var i = 0; i < this.original_length; ++i) 318 | for (var j = 0; j < this.original_width; ++j) 319 | if (this.marked[i][j] == 1) 320 | results.push([i, j]); 321 | 322 | return results; 323 | }; 324 | 325 | /** 326 | * Create an n×n matrix, populating it with the specific value. 327 | * 328 | * @param {Number} n Matrix dimensions 329 | * @param {Number} val Value to populate the matrix with 330 | * 331 | * @return {Array} An array of arrays representing the newly created matrix 332 | */ 333 | Munkres.prototype.__make_matrix = function(n, val) { 334 | var matrix = []; 335 | for (var i = 0; i < n; ++i) { 336 | matrix[i] = []; 337 | for (var j = 0; j < n; ++j) 338 | matrix[i][j] = val; 339 | } 340 | 341 | return matrix; 342 | }; 343 | 344 | /** 345 | * For each row of the matrix, find the smallest element and 346 | * subtract it from every element in its row. Go to Step 2. 347 | */ 348 | Munkres.prototype.__step1 = function() { 349 | for (var i = 0; i < this.n; ++i) { 350 | // Find the minimum value for this row and subtract that minimum 351 | // from every element in the row. 352 | var minval = Math.min.apply(Math, this.C[i]); 353 | 354 | for (var j = 0; j < this.n; ++j) 355 | this.C[i][j] -= minval; 356 | } 357 | 358 | return 2; 359 | }; 360 | 361 | /** 362 | * Find a zero (Z) in the resulting matrix. If there is no starred 363 | * zero in its row or column, star Z. Repeat for each element in the 364 | * matrix. Go to Step 3. 365 | */ 366 | Munkres.prototype.__step2 = function() { 367 | for (var i = 0; i < this.n; ++i) { 368 | for (var j = 0; j < this.n; ++j) { 369 | if (this.C[i][j] === 0 && 370 | !this.col_covered[j] && 371 | !this.row_covered[i]) 372 | { 373 | this.marked[i][j] = 1; 374 | this.col_covered[j] = true; 375 | this.row_covered[i] = true; 376 | break; 377 | } 378 | } 379 | } 380 | 381 | this.__clear_covers(); 382 | 383 | return 3; 384 | }; 385 | 386 | /** 387 | * Cover each column containing a starred zero. If K columns are 388 | * covered, the starred zeros describe a complete set of unique 389 | * assignments. In this case, Go to DONE, otherwise, Go to Step 4. 390 | */ 391 | Munkres.prototype.__step3 = function() { 392 | var count = 0; 393 | 394 | for (var i = 0; i < this.n; ++i) { 395 | for (var j = 0; j < this.n; ++j) { 396 | if (this.marked[i][j] == 1 && this.col_covered[j] == false) { 397 | this.col_covered[j] = true; 398 | ++count; 399 | } 400 | } 401 | } 402 | 403 | return (count >= this.n) ? 7 : 4; 404 | }; 405 | 406 | /** 407 | * Find a noncovered zero and prime it. If there is no starred zero 408 | * in the row containing this primed zero, Go to Step 5. Otherwise, 409 | * cover this row and uncover the column containing the starred 410 | * zero. Continue in this manner until there are no uncovered zeros 411 | * left. Save the smallest uncovered value and Go to Step 6. 412 | */ 413 | 414 | Munkres.prototype.__step4 = function() { 415 | var done = false; 416 | var row = -1, col = -1, star_col = -1; 417 | 418 | while (!done) { 419 | var z = this.__find_a_zero(); 420 | row = z[0]; 421 | col = z[1]; 422 | 423 | if (row < 0) 424 | return 6; 425 | 426 | this.marked[row][col] = 2; 427 | star_col = this.__find_star_in_row(row); 428 | if (star_col >= 0) { 429 | col = star_col; 430 | this.row_covered[row] = true; 431 | this.col_covered[col] = false; 432 | } else { 433 | this.Z0_r = row; 434 | this.Z0_c = col; 435 | return 5; 436 | } 437 | } 438 | }; 439 | 440 | /** 441 | * Construct a series of alternating primed and starred zeros as 442 | * follows. Let Z0 represent the uncovered primed zero found in Step 4. 443 | * Let Z1 denote the starred zero in the column of Z0 (if any). 444 | * Let Z2 denote the primed zero in the row of Z1 (there will always 445 | * be one). Continue until the series terminates at a primed zero 446 | * that has no starred zero in its column. Unstar each starred zero 447 | * of the series, star each primed zero of the series, erase all 448 | * primes and uncover every line in the matrix. Return to Step 3 449 | */ 450 | Munkres.prototype.__step5 = function() { 451 | var count = 0; 452 | 453 | this.path[count][0] = this.Z0_r; 454 | this.path[count][1] = this.Z0_c; 455 | var done = false; 456 | 457 | while (!done) { 458 | var row = this.__find_star_in_col(this.path[count][1]); 459 | if (row >= 0) { 460 | count++; 461 | this.path[count][0] = row; 462 | this.path[count][1] = this.path[count-1][1]; 463 | } else { 464 | done = true; 465 | } 466 | 467 | if (!done) { 468 | var col = this.__find_prime_in_row(this.path[count][0]); 469 | count++; 470 | this.path[count][0] = this.path[count-1][0]; 471 | this.path[count][1] = col; 472 | } 473 | } 474 | 475 | this.__convert_path(this.path, count); 476 | this.__clear_covers(); 477 | this.__erase_primes(); 478 | return 3; 479 | }; 480 | 481 | /** 482 | * Add the value found in Step 4 to every element of each covered 483 | * row, and subtract it from every element of each uncovered column. 484 | * Return to Step 4 without altering any stars, primes, or covered 485 | * lines. 486 | */ 487 | Munkres.prototype.__step6 = function() { 488 | var minval = this.__find_smallest(); 489 | 490 | for (var i = 0; i < this.n; ++i) { 491 | for (var j = 0; j < this.n; ++j) { 492 | if (this.row_covered[i]) 493 | this.C[i][j] += minval; 494 | if (!this.col_covered[j]) 495 | this.C[i][j] -= minval; 496 | } 497 | } 498 | 499 | return 4; 500 | }; 501 | 502 | /** 503 | * Find the smallest uncovered value in the matrix. 504 | * 505 | * @return {Number} The smallest uncovered value, or MAX_SIZE if no value was found 506 | */ 507 | Munkres.prototype.__find_smallest = function() { 508 | var minval = MAX_SIZE; 509 | 510 | for (var i = 0; i < this.n; ++i) 511 | for (var j = 0; j < this.n; ++j) 512 | if (!this.row_covered[i] && !this.col_covered[j]) 513 | if (minval > this.C[i][j]) 514 | minval = this.C[i][j]; 515 | 516 | return minval; 517 | }; 518 | 519 | /** 520 | * Find the first uncovered element with value 0. 521 | * 522 | * @return {Array} The indices of the found element or [-1, -1] if not found 523 | */ 524 | Munkres.prototype.__find_a_zero = function() { 525 | for (var i = 0; i < this.n; ++i) 526 | for (var j = 0; j < this.n; ++j) 527 | if (this.C[i][j] === 0 && 528 | !this.row_covered[i] && 529 | !this.col_covered[j]) 530 | return [i, j]; 531 | 532 | return [-1, -1]; 533 | }; 534 | 535 | /** 536 | * Find the first starred element in the specified row. Returns 537 | * the column index, or -1 if no starred element was found. 538 | * 539 | * @param {Number} row The index of the row to search 540 | * @return {Number} 541 | */ 542 | 543 | Munkres.prototype.__find_star_in_row = function(row) { 544 | for (var j = 0; j < this.n; ++j) 545 | if (this.marked[row][j] == 1) 546 | return j; 547 | 548 | return -1; 549 | }; 550 | 551 | /** 552 | * Find the first starred element in the specified column. 553 | * 554 | * @return {Number} The row index, or -1 if no starred element was found 555 | */ 556 | Munkres.prototype.__find_star_in_col = function(col) { 557 | for (var i = 0; i < this.n; ++i) 558 | if (this.marked[i][col] == 1) 559 | return i; 560 | 561 | return -1; 562 | }; 563 | 564 | /** 565 | * Find the first prime element in the specified row. 566 | * 567 | * @return {Number} The column index, or -1 if no prime element was found 568 | */ 569 | 570 | Munkres.prototype.__find_prime_in_row = function(row) { 571 | for (var j = 0; j < this.n; ++j) 572 | if (this.marked[row][j] == 2) 573 | return j; 574 | 575 | return -1; 576 | }; 577 | 578 | Munkres.prototype.__convert_path = function(path, count) { 579 | for (var i = 0; i <= count; ++i) 580 | this.marked[path[i][0]][path[i][1]] = 581 | (this.marked[path[i][0]][path[i][1]] == 1) ? 0 : 1; 582 | }; 583 | 584 | /** Clear all covered matrix cells */ 585 | Munkres.prototype.__clear_covers = function() { 586 | for (var i = 0; i < this.n; ++i) { 587 | this.row_covered[i] = false; 588 | this.col_covered[i] = false; 589 | } 590 | }; 591 | 592 | /** Erase all prime markings */ 593 | Munkres.prototype.__erase_primes = function() { 594 | for (var i = 0; i < this.n; ++i) 595 | for (var j = 0; j < this.n; ++j) 596 | if (this.marked[i][j] == 2) 597 | this.marked[i][j] = 0; 598 | }; 599 | 600 | // --------------------------------------------------------------------------- 601 | // Functions 602 | // --------------------------------------------------------------------------- 603 | 604 | /** 605 | * Create a cost matrix from a profit matrix by calling 606 | * 'inversion_function' to invert each value. The inversion 607 | * function must take one numeric argument (of any type) and return 608 | * another numeric argument which is presumed to be the cost inverse 609 | * of the original profit. 610 | * 611 | * This is a static method. Call it like this: 612 | * 613 | * cost_matrix = make_cost_matrix(matrix[, inversion_func]); 614 | * 615 | * For example: 616 | * 617 | * cost_matrix = make_cost_matrix(matrix, function(x) { return MAXIMUM - x; }); 618 | * 619 | * @param {Array} profit_matrix An array of arrays representing the matrix 620 | * to convert from a profit to a cost matrix 621 | * @param {Function} [inversion_function] The function to use to invert each 622 | * entry in the profit matrix 623 | * 624 | * @return {Array} The converted matrix 625 | */ 626 | function make_cost_matrix (profit_matrix, inversion_function) { 627 | var i, j; 628 | if (!inversion_function) { 629 | var maximum = -1.0/0.0; 630 | for (i = 0; i < profit_matrix.length; ++i) 631 | for (j = 0; j < profit_matrix[i].length; ++j) 632 | if (profit_matrix[i][j] > maximum) 633 | maximum = profit_matrix[i][j]; 634 | 635 | inversion_function = function(x) { return maximum - x; }; 636 | } 637 | 638 | var cost_matrix = []; 639 | 640 | for (i = 0; i < profit_matrix.length; ++i) { 641 | var row = profit_matrix[i]; 642 | cost_matrix[i] = []; 643 | 644 | for (j = 0; j < row.length; ++j) 645 | cost_matrix[i][j] = inversion_function(profit_matrix[i][j]); 646 | } 647 | 648 | return cost_matrix; 649 | } 650 | 651 | /** 652 | * Convenience function: Converts the contents of a matrix of integers 653 | * to a printable string. 654 | * 655 | * @param {Array} matrix The matrix to print 656 | * 657 | * @return {String} The formatted matrix 658 | */ 659 | function format_matrix(matrix) { 660 | var columnWidths = []; 661 | var i, j; 662 | for (i = 0; i < matrix.length; ++i) { 663 | for (j = 0; j < matrix[i].length; ++j) { 664 | var entryWidth = String(matrix[i][j]).length; 665 | 666 | if (!columnWidths[j] || entryWidth >= columnWidths[j]) 667 | columnWidths[j] = entryWidth; 668 | } 669 | } 670 | 671 | var formatted = ''; 672 | for (i = 0; i < matrix.length; ++i) { 673 | for (j = 0; j < matrix[i].length; ++j) { 674 | var s = String(matrix[i][j]); 675 | 676 | // pad at front with spaces 677 | while (s.length < columnWidths[j]) 678 | s = ' ' + s; 679 | 680 | formatted += s; 681 | 682 | // separate columns 683 | if (j != matrix[i].length - 1) 684 | formatted += ' '; 685 | } 686 | 687 | if (i != matrix[i].length - 1) 688 | formatted += '\n'; 689 | } 690 | 691 | return formatted; 692 | } 693 | 694 | // --------------------------------------------------------------------------- 695 | // Exports 696 | // --------------------------------------------------------------------------- 697 | 698 | function computeMunkres(cost_matrix, options) { 699 | var m = new Munkres(); 700 | return m.compute(cost_matrix, options); 701 | } 702 | 703 | computeMunkres.version = "1.2.2"; 704 | computeMunkres.format_matrix = format_matrix; 705 | computeMunkres.make_cost_matrix = make_cost_matrix; 706 | computeMunkres.Munkres = Munkres; // backwards compatibility 707 | 708 | if (typeof module !== 'undefined' && module.exports) { 709 | module.exports = computeMunkres; 710 | } 711 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "munkres-js", 3 | "main": "munkres.js", 4 | "version": "1.2.2", 5 | "homepage": "https://github.com/addaleax/munkres-js", 6 | "author": { 7 | "name": "Anna Henningsen", 8 | "email": "sqrt@entless.org" 9 | }, 10 | "description": "Munkres (aka Hungarian) algorithm for JS", 11 | "keywords": [ 12 | "assignment", 13 | "munkres", 14 | "hungarian" 15 | ], 16 | "license": "Apache-2.0 OR BSD-3-Clause", 17 | "bugs": { 18 | "url": "https://github.com/addaleax/munkres-js/issues" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/addaleax/munkres-js.git" 23 | }, 24 | "scripts": { 25 | "test": "nyc mocha" 26 | }, 27 | "devDependencies": { 28 | "coveralls": "^2.11.12", 29 | "mocha": "^3.0.2", 30 | "nyc": "^10.0.0", 31 | "should": "^11.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Mocha unit-test for munkress-js 3 | * 4 | * @author Erel Segal-Halevi 5 | * @since 2016-03 6 | */ 7 | 8 | var should = require('should'); 9 | var Munkres = require("../"); 10 | var m = new Munkres.Munkres(); 11 | 12 | describe('Munkres Algorithm', function() { 13 | it('handles singleton matrix', function() { 14 | var matrix = [[5]]; 15 | m.compute(matrix).should.eql([[0,0]]); 16 | }); 17 | 18 | it('handles negative singleton matrix', function() { 19 | var matrix = [[-5]]; 20 | m.compute(matrix).should.eql([[0,0]]); 21 | }); 22 | 23 | it('handles 2-by-2 matrix', function() { 24 | var matrix = [[5,3],[2,4]]; 25 | m.compute(matrix).should.eql([[0,1],[1,0]]); // smallest cost is 3+2=5 26 | }); 27 | 28 | it('handles 2-by-2 negative matrix', function() { 29 | var matrix = [[-5,-3],[-2,-4]]; 30 | m.compute(matrix).should.eql([[0,0],[1,1]]); 31 | }); 32 | 33 | it('handles 3-by-3 matrix', function() { 34 | var matrix = [[5,3,1],[2,4,6],[9,9,9]]; 35 | m.compute(matrix).should.eql([[0,2],[1,0],[2,1]]); // smallest cost is 1+2+9=12 36 | }); 37 | 38 | it('handles another 3-by-3 matrix', function() { 39 | var matrix = [ 40 | [400, 150, 400], 41 | [400, 450, 600], 42 | [300, 225, 300] 43 | ]; 44 | 45 | m.compute(matrix).should.eql([[0,1],[1,0],[2,2]]); 46 | }); 47 | 48 | it('handles 3-by-3 matrix with both positive and negative values', function() { 49 | var matrix = [[5,3,-1],[2,4,-6],[9,9,-9]]; 50 | m.compute(matrix).should.eql([[0,1],[1,0],[2,2]]); 51 | }); 52 | 53 | it('handles all-zero 3-by-3 matrix', function() { 54 | var matrix = [ 55 | [0,0,0], 56 | [0,0,0], 57 | [0,0,0] 58 | ]; 59 | 60 | m.compute(matrix).should.eql([[0,0],[1,1],[2,2]]); 61 | }); 62 | 63 | it('handles rectangular 3-by-4 matrix', function() { 64 | var matrix = [ 65 | [400, 150, 400, 1], 66 | [400, 450, 600, 2], 67 | [300, 225, 300, 3] 68 | ]; 69 | 70 | m.compute(matrix).should.eql([[0,1],[1,3],[2,0]]); 71 | }); 72 | 73 | it('handles rectangular 3-by-4 matrix, shorthand-style', function() { 74 | var matrix = [ 75 | [400, 150, 400, 1], 76 | [400, 450, 600, 2], 77 | [300, 225, 300, 3] 78 | ]; 79 | 80 | Munkres(matrix).should.eql([[0,1],[1,3],[2,0]]); 81 | }); 82 | 83 | it('converts profit-matrix to cost-matrix', function() { 84 | var profitmatrix = [[5,3],[2,4]]; 85 | var costmatrix = Munkres.make_cost_matrix(profitmatrix); 86 | 87 | m.compute(costmatrix).should.eql([[0,0],[1,1]]); // largest profit is 5+4=9 88 | }); 89 | }); 90 | --------------------------------------------------------------------------------