munkres-1.0.12/0000755000175000017500000000000013242364730014747 5ustar stefanorstefanor00000000000000munkres-1.0.12/munkres.egg-info/0000755000175000017500000000000013242364730020125 5ustar stefanorstefanor00000000000000munkres-1.0.12/munkres.egg-info/PKG-INFO0000644000175000017500000002722313242364730021230 0ustar stefanorstefanor00000000000000Metadata-Version: 1.1 Name: munkres Version: 1.0.12 Summary: munkres algorithm for the Assignment Problem Home-page: http://software.clapper.org/munkres/ Author: Brian Clapper Author-email: bmc@clapper.org License: Apache Software License Description-Content-Type: UNKNOWN Description: Introduction ============ The Munkres module provides an implementation of the Munkres algorithm (also called the Hungarian algorithm or the Kuhn-Munkres algorithm), useful for solving the Assignment Problem. Assignment Problem ================== Let *C* be an *n* by *n* matrix representing the costs of each of *n* workers to perform any of *n* jobs. The assignment problem is to assign jobs to workers in a way that minimizes the total cost. Since each worker can perform only one job and each job can be assigned to only one worker the assignments represent an independent set of the matrix *C*. One way to generate the optimal set is to create all permutations of the indexes necessary to traverse the matrix so that no row and column are used more than once. For instance, given this matrix (expressed in Python): matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] You could use this code to generate the traversal indexes: def permute(a, results): if len(a) == 1: results.insert(len(results), a) else: for i in range(0, len(a)): element = a[i] a_copy = [a[j] for j in range(0, len(a)) if j != i] subresults = [] permute(a_copy, subresults) for subresult in subresults: result = [element] + subresult results.insert(len(results), result) results = [] permute(range(len(matrix)), results) # [0, 1, 2] for a 3x3 matrix After the call to permute(), the results matrix would look like this: [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] You could then use that index matrix to loop over the original cost matrix and calculate the smallest cost of the combinations: minval = sys.maxsize for indexes in results: cost = 0 for row, col in enumerate(indexes): cost += matrix[row][col] minval = min(cost, minval) print minval While this approach works fine for small matrices, it does not scale. It executes in O(*n*!) time: Calculating the permutations for an *n*\ x\ *n* matrix requires *n*! operations. For a 12x12 matrix, that's 479,001,600 traversals. Even if you could manage to perform each traversal in just one millisecond, it would still take more than 133 hours to perform the entire traversal. A 20x20 matrix would take 2,432,902,008,176,640,000 operations. At an optimistic millisecond per operation, that's more than 77 million years. The Munkres algorithm runs in O(*n*\ ^3) time, rather than O(*n*!). This package provides an implementation of that algorithm. This version is based on http://csclab.murraystate.edu/~bob.pilgrim/445/munkres.html This version was written for Python by Brian Clapper from the algorithm at the above web site. (The ``Algorithm:Munkres`` Perl version, in CPAN, was clearly adapted from the same web site.) Usage ===== Construct a Munkres object: from munkres import Munkres m = Munkres() Then use it to compute the lowest cost assignment from a cost matrix. Here's a sample program: from munkres import Munkres, print_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] m = Munkres() indexes = m.compute(matrix) print_matrix(matrix, msg='Lowest cost through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total cost: %d' % total Running that program produces: Lowest cost through this matrix: [5, 9, 1] [10, 3, 2] [8, 7, 4] (0, 0) -> 5 (1, 1) -> 3 (2, 2) -> 4 total cost=12 The instantiated Munkres object can be used multiple times on different matrices. Non-square Cost Matrices ======================== The Munkres algorithm assumes that the cost matrix is square. However, it's possible to use a rectangular matrix if you first pad it with 0 values to make it square. This module automatically pads rectangular cost matrices to make them square. Notes: - The module operates on a *copy* of the caller's matrix, so any padding will not be seen by the caller. - The cost matrix must be rectangular or square. An irregular matrix will *not* work. Calculating Profit, Rather than Cost ==================================== The cost matrix is just that: A cost matrix. The Munkres algorithm finds the combination of elements (one from each row and column) that results in the smallest cost. It's also possible to use the algorithm to maximize profit. To do that, however, you have to convert your profit matrix to a cost matrix. The simplest way to do that is to subtract all elements from a large value. For example: from munkres import Munkres, print_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost_matrix = [] for row in matrix: cost_row = [] for col in row: cost_row += [sys.maxsize - col] cost_matrix += [cost_row] m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profit through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Running that program produces: Highest profit through this matrix: [5, 9, 1] [10, 3, 2] [8, 7, 4] (0, 1) -> 9 (1, 0) -> 10 (2, 2) -> 4 total profit=23 The ``munkres`` module provides a convenience method for creating a cost matrix from a profit matrix. By default, it calculates the maximum profit and subtracts every profit from it to obtain a cost. If, however, you need a more general function, you can provide the conversion function; but the convenience method takes care of the actual creation of the matrix: import munkres cost_matrix = munkres.make_cost_matrix( matrix, lambda profit: 1000.0 - math.sqrt(profit)) So, the above profit-calculation program can be recast as: from munkres import Munkres, print_matrix, make_cost_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost_matrix = make_cost_matrix(matrix) # cost_matrix == [[5, 1, 9], # [0, 7, 8], # [2, 3, 6]] m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profits through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Disallowed Assignments ====================== You can also mark assignments in your cost or profit matrix as disallowed. Simply use the munkres.DISALLOWED constant. from munkres import Munkres, print_matrix, make_cost_matrix, DISALLOWED matrix = [[5, 9, DISALLOWED], [10, DISALLOWED, 2], [8, 7, 4]] cost_matrix = make_cost_matrix(matrix, lambda cost: (sys.maxsize - cost) if (cost != DISALLOWED) else DISALLOWED) m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profit through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Running this program produces: Lowest cost through this matrix: [ 5, 9, D] [10, D, 2] [ 8, 7, 4] (0, 1) -> 9 (1, 0) -> 10 (2, 2) -> 4 total profit=23 References ========== 1. http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html 2. Harold W. Kuhn. The Hungarian Method for the assignment problem. *Naval Research Logistics Quarterly*, 2:83-97, 1955. 3. Harold W. Kuhn. Variants of the Hungarian method for assignment problems. *Naval Research Logistics Quarterly*, 3: 253-258, 1956. 4. Munkres, J. Algorithms for the Assignment and Transportation Problems. *Journal of the Society of Industrial and Applied Mathematics*, 5(1):32-38, March, 1957. 5. http://en.wikipedia.org/wiki/Hungarian_algorithm Copyright and License ===================== Copyright 2008-2016 Brian M. Clapper Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering :: Mathematics Classifier: Topic :: Software Development :: Libraries :: Python Modules munkres-1.0.12/munkres.egg-info/SOURCES.txt0000644000175000017500000000035313242364730022012 0ustar stefanorstefanor00000000000000CHANGELOG.md LICENSE.md MANIFEST.in README.md munkres.py setup.cfg setup.py munkres.egg-info/PKG-INFO munkres.egg-info/SOURCES.txt munkres.egg-info/dependency_links.txt munkres.egg-info/top_level.txt test/README.md test/test_munkres.pymunkres-1.0.12/munkres.egg-info/dependency_links.txt0000644000175000017500000000000113242364730024173 0ustar stefanorstefanor00000000000000 munkres-1.0.12/munkres.egg-info/top_level.txt0000644000175000017500000000001013242364730022646 0ustar stefanorstefanor00000000000000munkres munkres-1.0.12/test/0000755000175000017500000000000013242364730015726 5ustar stefanorstefanor00000000000000munkres-1.0.12/test/README.md0000644000175000017500000000022413242363561017204 0ustar stefanorstefanor00000000000000The tests in this directory are intended to be run via [Nose][]. ``` pip install nose nosetests ``` [Nose]: http://nose.readthedocs.io/en/latest/ munkres-1.0.12/test/test_munkres.py0000644000175000017500000001056313242363561021031 0ustar stefanorstefanor00000000000000from munkres import Munkres, DISALLOWED, UnsolvableMatrix import munkres from nose.tools import assert_equals, raises m = Munkres() def _get_cost(matrix): indices = m.compute(matrix) return sum([matrix[row][column] for row, column in indices]) def test_documented_example(): ''' Test the matrix in the documented example. ''' matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost = _get_cost(matrix) assert_equals(cost, 12) def test_5_x_5(): matrix = [[12, 9, 27, 10, 23], [7, 13, 13, 30, 19], [25, 18, 26, 11, 26], [9, 28, 26, 23, 13], [16, 16, 24, 6, 9]] cost = _get_cost(matrix) assert_equals(cost, 51) def test_10_x_10(): matrix = [[37, 34, 29, 26, 19, 8, 9, 23, 19, 29], [9, 28, 20, 8, 18, 20, 14, 33, 23, 14], [15, 26, 12, 28, 6, 17, 9, 13, 21, 7], [2, 8, 38, 36, 39, 5, 36, 2, 38, 27], [30, 3, 33, 16, 21, 39, 7, 23, 28, 36], [7, 5, 19, 22, 36, 36, 24, 19, 30, 2], [34, 20, 13, 36, 12, 33, 9, 10, 23, 5], [7, 37, 22, 39, 33, 39, 10, 3, 13, 26], [21, 25, 23, 39, 31, 37, 32, 33, 38, 1], [17, 34, 40, 10, 29, 37, 40, 3, 25, 3]] cost = _get_cost(matrix) assert_equals(cost, 66) def test_20_x_20(): matrix = [[5, 4, 3, 9, 8, 9, 3, 5, 6, 9, 4, 10, 3, 5, 6, 6, 1, 8, 10, 2], [10, 9, 9, 2, 8, 3, 9, 9, 10, 1, 7, 10, 8, 4, 2, 1, 4, 8, 4, 8], [10, 4, 4, 3, 1, 3, 5, 10, 6, 8, 6, 8, 4, 10, 7, 2, 4, 5, 1, 8], [2, 1, 4, 2, 3, 9, 3, 4, 7, 3, 4, 1, 3, 2, 9, 8, 6, 5, 7, 8], [3, 4, 4, 1, 4, 10, 1, 2, 6, 4, 5, 10, 2, 2, 3, 9, 10, 9, 9, 10], [1, 10, 1, 8, 1, 3, 1, 7, 1, 1, 2, 1, 2, 6, 3, 3, 4, 4, 8, 6], [1, 8, 7, 10, 10, 3, 4, 6, 1, 6, 6, 4, 9, 6, 9, 6, 4, 5, 4, 7], [8, 10, 3, 9, 4, 9, 3, 3, 4, 6, 4, 2, 6, 7, 7, 4, 4, 3, 4, 7], [1, 3, 8, 2, 6, 9, 2, 7, 4, 8, 10, 8, 10, 5, 1, 3, 10, 10, 2, 9], [2, 4, 1, 9, 2, 9, 7, 8, 2, 1, 4, 10, 5, 2, 7, 6, 5, 7, 2, 6], [4, 5, 1, 4, 2, 3, 3, 4, 1, 8, 8, 2, 6, 9, 5, 9, 6, 3, 9, 3], [3, 1, 1, 8, 6, 8, 8, 7, 9, 3, 2, 1, 8, 2, 4, 7, 3, 1, 2, 4], [5, 9, 8, 6, 10, 4, 10, 3, 4, 10, 10, 10, 1, 7, 8, 8, 7, 7, 8, 8], [1, 4, 6, 1, 6, 1, 2, 10, 5, 10, 2, 6, 2, 4, 5, 5, 3, 5, 1, 5], [5, 6, 9, 10, 6, 6, 10, 6, 4, 1, 5, 3, 9, 5, 2, 10, 9, 9, 5, 1], [10, 9, 4, 6, 9, 5, 3, 7, 10, 1, 6, 8, 1, 1, 10, 9, 5, 7, 7, 5], [2, 6, 6, 6, 6, 2, 9, 4, 7, 5, 3, 2, 10, 3, 4, 5, 10, 9, 1, 7], [5, 2, 4, 9, 8, 4, 8, 2, 4, 1, 3, 7, 6, 8, 1, 6, 8, 8, 10, 10], [9, 6, 3, 1, 8, 5, 7, 8, 7, 2, 1, 8, 2, 8, 3, 7, 4, 8, 7, 7], [8, 4, 4, 9, 7, 10, 6, 2, 1, 5, 8, 5, 1, 1, 1, 9, 1, 3, 5, 3]] cost = _get_cost(matrix) assert_equals(cost, 22) def test_disallowed(): matrix = [[5, 9, DISALLOWED], [10, DISALLOWED, 2], [8, DISALLOWED, 4]] cost = _get_cost(matrix) assert_equals(cost, 19) def test_profit(): profit_matrix = [[94, 66, 100, 18, 48], [51, 63, 97, 79, 11], [37, 53, 57, 78, 28], [59, 43, 97, 88, 48], [52, 19, 89, 60, 60]] import sys cost_matrix = munkres.make_cost_matrix( profit_matrix, lambda cost: sys.maxsize - cost ) indices = m.compute(cost_matrix) profit = sum([profit_matrix[row][column] for row, column in indices]) assert_equals(profit, 392) def test_irregular(): matrix = [[12, 26, 17], [49, 43, 36, 10, 5], [97, 9, 66, 34], [52, 42, 19, 36], [15, 93, 55, 80]] cost = _get_cost(matrix) assert_equals(cost, 43) def test_rectangular(): matrix = [[34, 26, 17, 12], [43, 43, 36, 10], [97, 47, 66, 34], [52, 42, 19, 36], [15, 93, 55, 80]] padded_matrix = m.pad_matrix(matrix, 0) padded_cost = _get_cost(padded_matrix) cost = _get_cost(matrix) assert_equals(padded_cost, cost) assert_equals(cost, 70) @raises(UnsolvableMatrix) def test_unsolvable(): matrix = [[5, 9, DISALLOWED], [10, DISALLOWED, 2], [DISALLOWED, DISALLOWED, DISALLOWED]] m.compute(matrix) munkres-1.0.12/CHANGELOG.md0000644000175000017500000000776013242363561016573 0ustar stefanorstefanor00000000000000# Change Log, munkres.py Version 1.0.12 (June, 2017) - Merged [Pull Request #11](https://github.com/bmc/munkres/pull/11), from [@brunokim](https://github.com/brunokim), which simplifies conversion of a profit matrix to a cost matrix, in the default case. - Merged [Pull Request #7](https://github.com/bmc/munkres/pull/7), from [@mdxs](https://github.com/mdxs), which fixes a message. - Added more tests. Version 1.0.11 (June, 2017) - Docs are now generated with [pdoc](https://github.com/BurntSushi/pdoc). - Merged [Pull Request 24](https://github.com/bmc/munkres/pull/24), from [@czlee](https://github.com/czlee): - Change to step 4: When it looks for a uncovered zero, rather than starting at row 0, column 0, it starts where it left off, i.e. at the last uncovered zero it found. Since it doesn't start at (0,0), when it gets to the last column it now loops around to the first, and exits unsuccessfully if it got back to where it started. This change reduces this reduces the solving time for (certain) large matrices. For instance, in tests, solving a matrix of size 394×394 goes from about 2 minutes to about 4 seconds. - Since Python 3 started cracking down on unnatural comparisons, the `DISALLOWED` constant added in [Pull Request 19](https://github.com/bmc/munkres/issues/19) no longer works. (It raises a TypeError for unorderable types, as is expected in Python 3.) Since this constant is meant to act like infinity, this modification just changes the two lines where it would otherwise try to make an illegal (in Python 3) comparison between a number and `DISALLOWED_OBJ()` and gets it to behave as if `DISALLOWED` is always larger. - Added Travis CI integration. - Added some unit tests. See `tests` and `tests/README.md`. Version 1.0.10 (May, 2017) - Updated `setup.py` to produce a wheel. Version 1.0.9 (Jan, 2017) - Fixed URL to original implementation. Addresses [Issue #4](https://github.com/bmc/munkres/issues/4). - Fixes from [@kylemcdonald](https://github.com/kylemcdonald): - `print_matrix()` no longer crashes on 0. Fixes [Issue #1](https://github.com/bmc/munkres/issues/4). - Fixed bug where step 3 could quit early. Fixes [Issue #16](https://github.com/bmc/munkres/issues/16). - Added step 2 break for a small optimization. - Added time bound to README. Addresses [Issue #15](https://github.com/bmc/munkres/issues/15). - Versioning will now adhere to [semantic version specification](http://semver.org). Version 1.0.8 (June, 2016) - License is now ASL. Version 1.0.7 (December, 2014) Fix from Stephan Porz (s.porz /at/ uni-bonn.de): - Fix pad_matrix: pad_value now actually used Version 1.0.6 (December, 2013) Fixes from Markus Padourek (markus.padourek /at/ gmail.com): - sys.maxsize fix and bump to 1.0.6 - Updated to Python 3.x Version 1.0.5.3 (2 August, 2009) - Fixed documentation of print_matrix() in module docs. Version 1.0.5.2 (30 June, 2008): - Incorporated some suggestions optimizations from Mark Summerfield (mark /at/ qtrac.eu) - Munkres.make_cost_matrix() is now deprecated, in favor of a module-level function. - The module now provides a print_matrix() convenience function. - Fixed some bugs related to the padding of non-square matrics. Version 1.0.5.1 (26 June, 2008) - Some minor doc changes. Version 1.0.5 (26 June, 2008) - Now handles non-square cost matrices by padding them with zeros. - Converted Epydocs to use reStructuredText. Version 1.0.4 (13 June, 2008) - Minor bug fix in main (tester) program in munkres.py Version 1.0.3 (16 March, 2008) - Minor change to prevent shadowing of built-in min() function. Thanks to Nelson Castillo (nelson /at/ emqbit.com) for pointing it out. Version 1.0.2 (21 February, 2008) - Fixed an overindexing bug reported by Chris Willmore (willmc rpi.edu) Version 1.0.1 (16 February, 2008) - Documentation now processed by Epydoc. Version 1.0 (January, 2008) - Initial release. munkres-1.0.12/LICENSE.md0000644000175000017500000000106413242363561016355 0ustar stefanorstefanor00000000000000Copyright © 2008-2017 Brian M. Clapper Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. munkres-1.0.12/MANIFEST.in0000644000175000017500000000010613242364444016504 0ustar stefanorstefanor00000000000000recursive-include test *.md include LICENSE.md README.md CHANGELOG.md munkres-1.0.12/README.md0000644000175000017500000000232513242363561016231 0ustar stefanorstefanor00000000000000Munkres implementation for Python --------------------------------- ## Introduction The Munkres module provides an O(n^3) implementation of the Munkres algorithm (also called the [Hungarian algorithm][] or the Kuhn-Munkres algorithm). The algorithm models an assignment problem as an NxM cost matrix, where each element represents the cost of assigning the ith worker to the jth job, and it figures out the least-cost solution, choosing a single item from each row and column in the matrix, such that no row and no column are used more than once. This particular implementation is based on . [Hungarian algorithm]: http://en.wikipedia.org/wiki/Hungarian_algorithm See the docs in munkres.py and the [home page][] for more details. [home page]: http://software.clapper.org/munkres/ ## Copyright © 2008-2017 Brian M. Clapper ## License Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE.md) for details. munkres-1.0.12/munkres.py0000644000175000017500000006471413242363561017022 0ustar stefanorstefanor00000000000000#!/usr/bin/env python # -*- coding: iso-8859-1 -*- # Documentation is intended to be processed by Epydoc. """ Introduction ============ The Munkres module provides an implementation of the Munkres algorithm (also called the Hungarian algorithm or the Kuhn-Munkres algorithm), useful for solving the Assignment Problem. Assignment Problem ================== Let *C* be an *n* by *n* matrix representing the costs of each of *n* workers to perform any of *n* jobs. The assignment problem is to assign jobs to workers in a way that minimizes the total cost. Since each worker can perform only one job and each job can be assigned to only one worker the assignments represent an independent set of the matrix *C*. One way to generate the optimal set is to create all permutations of the indexes necessary to traverse the matrix so that no row and column are used more than once. For instance, given this matrix (expressed in Python): matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] You could use this code to generate the traversal indexes: def permute(a, results): if len(a) == 1: results.insert(len(results), a) else: for i in range(0, len(a)): element = a[i] a_copy = [a[j] for j in range(0, len(a)) if j != i] subresults = [] permute(a_copy, subresults) for subresult in subresults: result = [element] + subresult results.insert(len(results), result) results = [] permute(range(len(matrix)), results) # [0, 1, 2] for a 3x3 matrix After the call to permute(), the results matrix would look like this: [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] You could then use that index matrix to loop over the original cost matrix and calculate the smallest cost of the combinations: minval = sys.maxsize for indexes in results: cost = 0 for row, col in enumerate(indexes): cost += matrix[row][col] minval = min(cost, minval) print minval While this approach works fine for small matrices, it does not scale. It executes in O(*n*!) time: Calculating the permutations for an *n*\ x\ *n* matrix requires *n*! operations. For a 12x12 matrix, that's 479,001,600 traversals. Even if you could manage to perform each traversal in just one millisecond, it would still take more than 133 hours to perform the entire traversal. A 20x20 matrix would take 2,432,902,008,176,640,000 operations. At an optimistic millisecond per operation, that's more than 77 million years. The Munkres algorithm runs in O(*n*\ ^3) time, rather than O(*n*!). This package provides an implementation of that algorithm. This version is based on http://csclab.murraystate.edu/~bob.pilgrim/445/munkres.html This version was written for Python by Brian Clapper from the algorithm at the above web site. (The ``Algorithm:Munkres`` Perl version, in CPAN, was clearly adapted from the same web site.) Usage ===== Construct a Munkres object: from munkres import Munkres m = Munkres() Then use it to compute the lowest cost assignment from a cost matrix. Here's a sample program: from munkres import Munkres, print_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] m = Munkres() indexes = m.compute(matrix) print_matrix(matrix, msg='Lowest cost through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total cost: %d' % total Running that program produces: Lowest cost through this matrix: [5, 9, 1] [10, 3, 2] [8, 7, 4] (0, 0) -> 5 (1, 1) -> 3 (2, 2) -> 4 total cost=12 The instantiated Munkres object can be used multiple times on different matrices. Non-square Cost Matrices ======================== The Munkres algorithm assumes that the cost matrix is square. However, it's possible to use a rectangular matrix if you first pad it with 0 values to make it square. This module automatically pads rectangular cost matrices to make them square. Notes: - The module operates on a *copy* of the caller's matrix, so any padding will not be seen by the caller. - The cost matrix must be rectangular or square. An irregular matrix will *not* work. Calculating Profit, Rather than Cost ==================================== The cost matrix is just that: A cost matrix. The Munkres algorithm finds the combination of elements (one from each row and column) that results in the smallest cost. It's also possible to use the algorithm to maximize profit. To do that, however, you have to convert your profit matrix to a cost matrix. The simplest way to do that is to subtract all elements from a large value. For example: from munkres import Munkres, print_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost_matrix = [] for row in matrix: cost_row = [] for col in row: cost_row += [sys.maxsize - col] cost_matrix += [cost_row] m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profit through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Running that program produces: Highest profit through this matrix: [5, 9, 1] [10, 3, 2] [8, 7, 4] (0, 1) -> 9 (1, 0) -> 10 (2, 2) -> 4 total profit=23 The ``munkres`` module provides a convenience method for creating a cost matrix from a profit matrix. By default, it calculates the maximum profit and subtracts every profit from it to obtain a cost. If, however, you need a more general function, you can provide the conversion function; but the convenience method takes care of the actual creation of the matrix: import munkres cost_matrix = munkres.make_cost_matrix( matrix, lambda profit: 1000.0 - math.sqrt(profit)) So, the above profit-calculation program can be recast as: from munkres import Munkres, print_matrix, make_cost_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost_matrix = make_cost_matrix(matrix) # cost_matrix == [[5, 1, 9], # [0, 7, 8], # [2, 3, 6]] m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profits through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Disallowed Assignments ====================== You can also mark assignments in your cost or profit matrix as disallowed. Simply use the munkres.DISALLOWED constant. from munkres import Munkres, print_matrix, make_cost_matrix, DISALLOWED matrix = [[5, 9, DISALLOWED], [10, DISALLOWED, 2], [8, 7, 4]] cost_matrix = make_cost_matrix(matrix, lambda cost: (sys.maxsize - cost) if (cost != DISALLOWED) else DISALLOWED) m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profit through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Running this program produces: Lowest cost through this matrix: [ 5, 9, D] [10, D, 2] [ 8, 7, 4] (0, 1) -> 9 (1, 0) -> 10 (2, 2) -> 4 total profit=23 References ========== 1. http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html 2. Harold W. Kuhn. The Hungarian Method for the assignment problem. *Naval Research Logistics Quarterly*, 2:83-97, 1955. 3. Harold W. Kuhn. Variants of the Hungarian method for assignment problems. *Naval Research Logistics Quarterly*, 3: 253-258, 1956. 4. Munkres, J. Algorithms for the Assignment and Transportation Problems. *Journal of the Society of Industrial and Applied Mathematics*, 5(1):32-38, March, 1957. 5. http://en.wikipedia.org/wiki/Hungarian_algorithm Copyright and License ===================== Copyright 2008-2016 Brian M. Clapper Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ __docformat__ = 'restructuredtext' # --------------------------------------------------------------------------- # Imports # --------------------------------------------------------------------------- import sys import copy # --------------------------------------------------------------------------- # Exports # --------------------------------------------------------------------------- __all__ = ['Munkres', 'make_cost_matrix', 'DISALLOWED'] # --------------------------------------------------------------------------- # Globals # --------------------------------------------------------------------------- # Info about the module __version__ = "1.0.12" __author__ = "Brian Clapper, bmc@clapper.org" __url__ = "http://software.clapper.org/munkres/" __copyright__ = "(c) 2008-2017 Brian M. Clapper" __license__ = "Apache Software License" # Constants class DISALLOWED_OBJ(object): pass DISALLOWED = DISALLOWED_OBJ() DISALLOWED_PRINTVAL = "D" # --------------------------------------------------------------------------- # Exceptions # --------------------------------------------------------------------------- class UnsolvableMatrix(Exception): """ Exception raised for unsolvable matrices """ pass # --------------------------------------------------------------------------- # Classes # --------------------------------------------------------------------------- class Munkres: """ Calculate the Munkres solution to the classical assignment problem. See the module documentation for usage. """ def __init__(self): """Create a new instance""" self.C = None self.row_covered = [] self.col_covered = [] self.n = 0 self.Z0_r = 0 self.Z0_c = 0 self.marked = None self.path = None def make_cost_matrix(profit_matrix, inversion_function): """ **DEPRECATED** Please use the module function ``make_cost_matrix()``. """ import munkres return munkres.make_cost_matrix(profit_matrix, inversion_function) make_cost_matrix = staticmethod(make_cost_matrix) def pad_matrix(self, matrix, pad_value=0): """ Pad a possibly non-square matrix to make it square. :Parameters: matrix : list of lists matrix to pad pad_value : int value to use to pad the matrix :rtype: list of lists :return: a new, possibly padded, matrix """ max_columns = 0 total_rows = len(matrix) for row in matrix: max_columns = max(max_columns, len(row)) total_rows = max(max_columns, total_rows) new_matrix = [] for row in matrix: row_len = len(row) new_row = row[:] if total_rows > row_len: # Row too short. Pad it. new_row += [pad_value] * (total_rows - row_len) new_matrix += [new_row] while len(new_matrix) < total_rows: new_matrix += [[pad_value] * total_rows] return new_matrix def compute(self, cost_matrix): """ Compute the indexes for the lowest-cost pairings between rows and columns in the database. Returns a list of (row, column) tuples that can be used to traverse the matrix. :Parameters: cost_matrix : list of lists The cost matrix. If this cost matrix is not square, it will be padded with zeros, via a call to ``pad_matrix()``. (This method does *not* modify the caller's matrix. It operates on a copy of the matrix.) **WARNING**: This code handles square and rectangular matrices. It does *not* handle irregular matrices. :rtype: list :return: A list of ``(row, column)`` tuples that describe the lowest cost path through the matrix """ self.C = self.pad_matrix(cost_matrix) self.n = len(self.C) self.original_length = len(cost_matrix) self.original_width = len(cost_matrix[0]) self.row_covered = [False for i in range(self.n)] self.col_covered = [False for i in range(self.n)] self.Z0_r = 0 self.Z0_c = 0 self.path = self.__make_matrix(self.n * 2, 0) self.marked = self.__make_matrix(self.n, 0) done = False step = 1 steps = { 1 : self.__step1, 2 : self.__step2, 3 : self.__step3, 4 : self.__step4, 5 : self.__step5, 6 : self.__step6 } while not done: try: func = steps[step] step = func() except KeyError: done = True # Look for the starred columns results = [] for i in range(self.original_length): for j in range(self.original_width): if self.marked[i][j] == 1: results += [(i, j)] return results def __copy_matrix(self, matrix): """Return an exact copy of the supplied matrix""" return copy.deepcopy(matrix) def __make_matrix(self, n, val): """Create an *n*x*n* matrix, populating it with the specific value.""" matrix = [] for i in range(n): matrix += [[val for j in range(n)]] return matrix def __step1(self): """ For each row of the matrix, find the smallest element and subtract it from every element in its row. Go to Step 2. """ C = self.C n = self.n for i in range(n): vals = [x for x in self.C[i] if x is not DISALLOWED] if len(vals) == 0: # All values in this row are DISALLOWED. This matrix is # unsolvable. raise UnsolvableMatrix( "Row {0} is entirely DISALLOWED.".format(i) ) minval = min(vals) # Find the minimum value for this row and subtract that minimum # from every element in the row. for j in range(n): if self.C[i][j] is not DISALLOWED: self.C[i][j] -= minval return 2 def __step2(self): """ Find a zero (Z) in the resulting matrix. If there is no starred zero in its row or column, star Z. Repeat for each element in the matrix. Go to Step 3. """ n = self.n for i in range(n): for j in range(n): if (self.C[i][j] == 0) and \ (not self.col_covered[j]) and \ (not self.row_covered[i]): self.marked[i][j] = 1 self.col_covered[j] = True self.row_covered[i] = True break self.__clear_covers() return 3 def __step3(self): """ Cover each column containing a starred zero. If K columns are covered, the starred zeros describe a complete set of unique assignments. In this case, Go to DONE, otherwise, Go to Step 4. """ n = self.n count = 0 for i in range(n): for j in range(n): if self.marked[i][j] == 1 and not self.col_covered[j]: self.col_covered[j] = True count += 1 if count >= n: step = 7 # done else: step = 4 return step def __step4(self): """ Find a noncovered zero and prime it. If there is no starred zero in the row containing this primed zero, Go to Step 5. Otherwise, cover this row and uncover the column containing the starred zero. Continue in this manner until there are no uncovered zeros left. Save the smallest uncovered value and Go to Step 6. """ step = 0 done = False row = 0 col = 0 star_col = -1 while not done: (row, col) = self.__find_a_zero(row, col) if row < 0: done = True step = 6 else: self.marked[row][col] = 2 star_col = self.__find_star_in_row(row) if star_col >= 0: col = star_col self.row_covered[row] = True self.col_covered[col] = False else: done = True self.Z0_r = row self.Z0_c = col step = 5 return step def __step5(self): """ Construct a series of alternating primed and starred zeros as follows. Let Z0 represent the uncovered primed zero found in Step 4. Let Z1 denote the starred zero in the column of Z0 (if any). Let Z2 denote the primed zero in the row of Z1 (there will always be one). Continue until the series terminates at a primed zero that has no starred zero in its column. Unstar each starred zero of the series, star each primed zero of the series, erase all primes and uncover every line in the matrix. Return to Step 3 """ count = 0 path = self.path path[count][0] = self.Z0_r path[count][1] = self.Z0_c done = False while not done: row = self.__find_star_in_col(path[count][1]) if row >= 0: count += 1 path[count][0] = row path[count][1] = path[count-1][1] else: done = True if not done: col = self.__find_prime_in_row(path[count][0]) count += 1 path[count][0] = path[count-1][0] path[count][1] = col self.__convert_path(path, count) self.__clear_covers() self.__erase_primes() return 3 def __step6(self): """ Add the value found in Step 4 to every element of each covered row, and subtract it from every element of each uncovered column. Return to Step 4 without altering any stars, primes, or covered lines. """ minval = self.__find_smallest() events = 0 # track actual changes to matrix for i in range(self.n): for j in range(self.n): if self.C[i][j] is DISALLOWED: continue if self.row_covered[i]: self.C[i][j] += minval events += 1 if not self.col_covered[j]: self.C[i][j] -= minval events += 1 if self.row_covered[i] and not self.col_covered[j]: events -= 2 # change reversed, no real difference if (events == 0): raise UnsolvableMatrix("Matrix cannot be solved!") return 4 def __find_smallest(self): """Find the smallest uncovered value in the matrix.""" minval = sys.maxsize for i in range(self.n): for j in range(self.n): if (not self.row_covered[i]) and (not self.col_covered[j]): if self.C[i][j] is not DISALLOWED and minval > self.C[i][j]: minval = self.C[i][j] return minval def __find_a_zero(self, i0=0, j0=0): """Find the first uncovered element with value 0""" row = -1 col = -1 i = i0 n = self.n done = False while not done: j = j0 while True: if (self.C[i][j] == 0) and \ (not self.row_covered[i]) and \ (not self.col_covered[j]): row = i col = j done = True j = (j + 1) % n if j == j0: break i = (i + 1) % n if i == i0: done = True return (row, col) def __find_star_in_row(self, row): """ Find the first starred element in the specified row. Returns the column index, or -1 if no starred element was found. """ col = -1 for j in range(self.n): if self.marked[row][j] == 1: col = j break return col def __find_star_in_col(self, col): """ Find the first starred element in the specified row. Returns the row index, or -1 if no starred element was found. """ row = -1 for i in range(self.n): if self.marked[i][col] == 1: row = i break return row def __find_prime_in_row(self, row): """ Find the first prime element in the specified row. Returns the column index, or -1 if no starred element was found. """ col = -1 for j in range(self.n): if self.marked[row][j] == 2: col = j break return col def __convert_path(self, path, count): for i in range(count+1): if self.marked[path[i][0]][path[i][1]] == 1: self.marked[path[i][0]][path[i][1]] = 0 else: self.marked[path[i][0]][path[i][1]] = 1 def __clear_covers(self): """Clear all covered matrix cells""" for i in range(self.n): self.row_covered[i] = False self.col_covered[i] = False def __erase_primes(self): """Erase all prime markings""" for i in range(self.n): for j in range(self.n): if self.marked[i][j] == 2: self.marked[i][j] = 0 # --------------------------------------------------------------------------- # Functions # --------------------------------------------------------------------------- def make_cost_matrix(profit_matrix, inversion_function=None): """ Create a cost matrix from a profit matrix by calling 'inversion_function' to invert each value. The inversion function must take one numeric argument (of any type) and return another numeric argument which is presumed to be the cost inverse of the original profit. In case the inversion function is not provided, calculate it as max(matrix) - matrix. This is a static method. Call it like this: .. python: cost_matrix = Munkres.make_cost_matrix(matrix, inversion_func) For example: .. python: cost_matrix = Munkres.make_cost_matrix(matrix, lambda x : sys.maxsize - x) :Parameters: profit_matrix : list of lists The matrix to convert from a profit to a cost matrix inversion_function : function The function to use to invert each entry in the profit matrix. In case it is not provided, calculate it as max(matrix) - matrix. :rtype: list of lists :return: The converted matrix """ if not inversion_function: maximum = max(max(row) for row in profit_matrix) inversion_function = lambda x: maximum - x cost_matrix = [] for row in profit_matrix: cost_matrix.append([inversion_function(value) for value in row]) return cost_matrix def print_matrix(matrix, msg=None): """ Convenience function: Displays the contents of a matrix of integers. :Parameters: matrix : list of lists Matrix to print msg : str Optional message to print before displaying the matrix """ import math if msg is not None: print(msg) # Calculate the appropriate format width. width = 0 for row in matrix: for val in row: if val is DISALLOWED: val = DISALLOWED_PRINTVAL width = max(width, len(str(val))) # Make the format string format = ('%%%d' % width) # Print the matrix for row in matrix: sep = '[' for val in row: if val is DISALLOWED: formatted = ((format + 's') % DISALLOWED_PRINTVAL) else: formatted = ((format + 'd') % val) sys.stdout.write(sep + formatted) sep = ', ' sys.stdout.write(']\n') # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- if __name__ == '__main__': matrices = [ # Square ([[400, 150, 400], [400, 450, 600], [300, 225, 300]], 850), # expected cost # Rectangular variant ([[400, 150, 400, 1], [400, 450, 600, 2], [300, 225, 300, 3]], 452), # expected cost # Square ([[10, 10, 8], [9, 8, 1], [9, 7, 4]], 18), # Rectangular variant ([[10, 10, 8, 11], [9, 8, 1, 1], [9, 7, 4, 10]], 15), # Rectangular with DISALLOWED ([[4, 5, 6, DISALLOWED], [1, 9, 12, 11], [DISALLOWED, 5, 4, DISALLOWED], [12, 12, 12, 10]], 20), # DISALLOWED to force pairings ([[1, DISALLOWED, DISALLOWED, DISALLOWED], [DISALLOWED, 2, DISALLOWED, DISALLOWED], [DISALLOWED, DISALLOWED, 3, DISALLOWED], [DISALLOWED, DISALLOWED, DISALLOWED, 4]], 10)] m = Munkres() for cost_matrix, expected_total in matrices: print_matrix(cost_matrix, msg='cost matrix') indexes = m.compute(cost_matrix) total_cost = 0 for r, c in indexes: x = cost_matrix[r][c] total_cost += x print('(%d, %d) -> %d' % (r, c, x)) print('lowest cost=%d' % total_cost) assert expected_total == total_cost munkres-1.0.12/setup.cfg0000644000175000017500000000014113242364730016564 0ustar stefanorstefanor00000000000000[sdist] formats = gztar, zip [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 munkres-1.0.12/setup.py0000644000175000017500000000246213242363561016466 0ustar stefanorstefanor00000000000000#!/usr/bin/env python # # Distutils setup script for Munkres # --------------------------------------------------------------------------- from setuptools import setup import re import os import sys import imp # Load the data. here = os.path.dirname(os.path.abspath(sys.argv[0])) sys.path = [here] + sys.path mf = os.path.join(here, 'munkres.py') munkres = imp.load_module('munkres', open(mf), mf, ('__init__.py', 'r', imp.PY_SOURCE)) long_description = munkres.__doc__ version = str(munkres.__version__) (author, email) = re.match('^(.*),\s*(.*)$', munkres.__author__).groups() url = munkres.__url__ license = munkres.__license__ # Run setup setup( name="munkres", version=version, description="munkres algorithm for the Assignment Problem", long_description=long_description, url=url, license=license, author=author, author_email=email, py_modules=["munkres"], classifiers = [ 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Scientific/Engineering :: Mathematics', 'Topic :: Software Development :: Libraries :: Python Modules' ] ) munkres-1.0.12/PKG-INFO0000644000175000017500000002722313242364730016052 0ustar stefanorstefanor00000000000000Metadata-Version: 1.1 Name: munkres Version: 1.0.12 Summary: munkres algorithm for the Assignment Problem Home-page: http://software.clapper.org/munkres/ Author: Brian Clapper Author-email: bmc@clapper.org License: Apache Software License Description-Content-Type: UNKNOWN Description: Introduction ============ The Munkres module provides an implementation of the Munkres algorithm (also called the Hungarian algorithm or the Kuhn-Munkres algorithm), useful for solving the Assignment Problem. Assignment Problem ================== Let *C* be an *n* by *n* matrix representing the costs of each of *n* workers to perform any of *n* jobs. The assignment problem is to assign jobs to workers in a way that minimizes the total cost. Since each worker can perform only one job and each job can be assigned to only one worker the assignments represent an independent set of the matrix *C*. One way to generate the optimal set is to create all permutations of the indexes necessary to traverse the matrix so that no row and column are used more than once. For instance, given this matrix (expressed in Python): matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] You could use this code to generate the traversal indexes: def permute(a, results): if len(a) == 1: results.insert(len(results), a) else: for i in range(0, len(a)): element = a[i] a_copy = [a[j] for j in range(0, len(a)) if j != i] subresults = [] permute(a_copy, subresults) for subresult in subresults: result = [element] + subresult results.insert(len(results), result) results = [] permute(range(len(matrix)), results) # [0, 1, 2] for a 3x3 matrix After the call to permute(), the results matrix would look like this: [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] You could then use that index matrix to loop over the original cost matrix and calculate the smallest cost of the combinations: minval = sys.maxsize for indexes in results: cost = 0 for row, col in enumerate(indexes): cost += matrix[row][col] minval = min(cost, minval) print minval While this approach works fine for small matrices, it does not scale. It executes in O(*n*!) time: Calculating the permutations for an *n*\ x\ *n* matrix requires *n*! operations. For a 12x12 matrix, that's 479,001,600 traversals. Even if you could manage to perform each traversal in just one millisecond, it would still take more than 133 hours to perform the entire traversal. A 20x20 matrix would take 2,432,902,008,176,640,000 operations. At an optimistic millisecond per operation, that's more than 77 million years. The Munkres algorithm runs in O(*n*\ ^3) time, rather than O(*n*!). This package provides an implementation of that algorithm. This version is based on http://csclab.murraystate.edu/~bob.pilgrim/445/munkres.html This version was written for Python by Brian Clapper from the algorithm at the above web site. (The ``Algorithm:Munkres`` Perl version, in CPAN, was clearly adapted from the same web site.) Usage ===== Construct a Munkres object: from munkres import Munkres m = Munkres() Then use it to compute the lowest cost assignment from a cost matrix. Here's a sample program: from munkres import Munkres, print_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] m = Munkres() indexes = m.compute(matrix) print_matrix(matrix, msg='Lowest cost through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total cost: %d' % total Running that program produces: Lowest cost through this matrix: [5, 9, 1] [10, 3, 2] [8, 7, 4] (0, 0) -> 5 (1, 1) -> 3 (2, 2) -> 4 total cost=12 The instantiated Munkres object can be used multiple times on different matrices. Non-square Cost Matrices ======================== The Munkres algorithm assumes that the cost matrix is square. However, it's possible to use a rectangular matrix if you first pad it with 0 values to make it square. This module automatically pads rectangular cost matrices to make them square. Notes: - The module operates on a *copy* of the caller's matrix, so any padding will not be seen by the caller. - The cost matrix must be rectangular or square. An irregular matrix will *not* work. Calculating Profit, Rather than Cost ==================================== The cost matrix is just that: A cost matrix. The Munkres algorithm finds the combination of elements (one from each row and column) that results in the smallest cost. It's also possible to use the algorithm to maximize profit. To do that, however, you have to convert your profit matrix to a cost matrix. The simplest way to do that is to subtract all elements from a large value. For example: from munkres import Munkres, print_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost_matrix = [] for row in matrix: cost_row = [] for col in row: cost_row += [sys.maxsize - col] cost_matrix += [cost_row] m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profit through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Running that program produces: Highest profit through this matrix: [5, 9, 1] [10, 3, 2] [8, 7, 4] (0, 1) -> 9 (1, 0) -> 10 (2, 2) -> 4 total profit=23 The ``munkres`` module provides a convenience method for creating a cost matrix from a profit matrix. By default, it calculates the maximum profit and subtracts every profit from it to obtain a cost. If, however, you need a more general function, you can provide the conversion function; but the convenience method takes care of the actual creation of the matrix: import munkres cost_matrix = munkres.make_cost_matrix( matrix, lambda profit: 1000.0 - math.sqrt(profit)) So, the above profit-calculation program can be recast as: from munkres import Munkres, print_matrix, make_cost_matrix matrix = [[5, 9, 1], [10, 3, 2], [8, 7, 4]] cost_matrix = make_cost_matrix(matrix) # cost_matrix == [[5, 1, 9], # [0, 7, 8], # [2, 3, 6]] m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profits through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Disallowed Assignments ====================== You can also mark assignments in your cost or profit matrix as disallowed. Simply use the munkres.DISALLOWED constant. from munkres import Munkres, print_matrix, make_cost_matrix, DISALLOWED matrix = [[5, 9, DISALLOWED], [10, DISALLOWED, 2], [8, 7, 4]] cost_matrix = make_cost_matrix(matrix, lambda cost: (sys.maxsize - cost) if (cost != DISALLOWED) else DISALLOWED) m = Munkres() indexes = m.compute(cost_matrix) print_matrix(matrix, msg='Highest profit through this matrix:') total = 0 for row, column in indexes: value = matrix[row][column] total += value print '(%d, %d) -> %d' % (row, column, value) print 'total profit=%d' % total Running this program produces: Lowest cost through this matrix: [ 5, 9, D] [10, D, 2] [ 8, 7, 4] (0, 1) -> 9 (1, 0) -> 10 (2, 2) -> 4 total profit=23 References ========== 1. http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html 2. Harold W. Kuhn. The Hungarian Method for the assignment problem. *Naval Research Logistics Quarterly*, 2:83-97, 1955. 3. Harold W. Kuhn. Variants of the Hungarian method for assignment problems. *Naval Research Logistics Quarterly*, 3: 253-258, 1956. 4. Munkres, J. Algorithms for the Assignment and Transportation Problems. *Journal of the Society of Industrial and Applied Mathematics*, 5(1):32-38, March, 1957. 5. http://en.wikipedia.org/wiki/Hungarian_algorithm Copyright and License ===================== Copyright 2008-2016 Brian M. Clapper Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering :: Mathematics Classifier: Topic :: Software Development :: Libraries :: Python Modules