mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
Initial Commit
This commit is contained in:
commit
88daa3fb91
1311 changed files with 256240 additions and 0 deletions
786
lib/munkres.py
Normal file
786
lib/munkres.py
Normal file
|
@ -0,0 +1,786 @@
|
|||
#!/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*\ x\ *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::
|
||||
|
||||
n = len(matrix)
|
||||
minval = sys.maxsize
|
||||
for row in range(n):
|
||||
cost = 0
|
||||
for col in range(n):
|
||||
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://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html.
|
||||
|
||||
This version was written for Python by Brian Clapper from the (Ada) 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. Since it doesn't know whether the matrix contains
|
||||
floating point numbers, decimals, or integers, you have to provide the
|
||||
conversion function; but the convenience method takes care of the actual
|
||||
creation of the cost matrix::
|
||||
|
||||
import munkres
|
||||
|
||||
cost_matrix = munkres.make_cost_matrix(matrix,
|
||||
lambda cost: sys.maxsize - cost)
|
||||
|
||||
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, lambda cost: sys.maxsize - cost)
|
||||
m = Munkres()
|
||||
indexes = m.compute(cost_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 profit=%d' % total
|
||||
|
||||
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
|
||||
=====================
|
||||
|
||||
This software is released under a BSD license, adapted from
|
||||
<http://opensource.org/licenses/bsd-license.php>
|
||||
|
||||
Copyright (c) 2008 Brian M. Clapper
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name "clapper.org" nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Imports
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import copy
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exports
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
__all__ = ['Munkres', 'make_cost_matrix']
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Globals
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Info about the module
|
||||
__version__ = "1.0.6"
|
||||
__author__ = "Brian Clapper, bmc@clapper.org"
|
||||
__url__ = "http://software.clapper.org/munkres/"
|
||||
__copyright__ = "(c) 2008 Brian M. Clapper"
|
||||
__license__ = "BSD-style license"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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 += [0] * (total_rows - row_len)
|
||||
new_matrix += [new_row]
|
||||
|
||||
while len(new_matrix) < total_rows:
|
||||
new_matrix += [[0] * 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):
|
||||
minval = min(self.C[i])
|
||||
# Find the minimum value for this row and subtract that minimum
|
||||
# from every element in the row.
|
||||
for j in range(n):
|
||||
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
|
||||
|
||||
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:
|
||||
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 = -1
|
||||
col = -1
|
||||
star_col = -1
|
||||
while not done:
|
||||
(row, col) = self.__find_a_zero()
|
||||
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()
|
||||
for i in range(self.n):
|
||||
for j in range(self.n):
|
||||
if self.row_covered[i]:
|
||||
self.C[i][j] += minval
|
||||
if not self.col_covered[j]:
|
||||
self.C[i][j] -= minval
|
||||
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 minval > self.C[i][j]:
|
||||
minval = self.C[i][j]
|
||||
return minval
|
||||
|
||||
def __find_a_zero(self):
|
||||
"""Find the first uncovered element with value 0"""
|
||||
row = -1
|
||||
col = -1
|
||||
i = 0
|
||||
n = self.n
|
||||
done = False
|
||||
|
||||
while not done:
|
||||
j = 0
|
||||
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 += 1
|
||||
if j >= n:
|
||||
break
|
||||
i += 1
|
||||
if i >= n:
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
:rtype: list of lists
|
||||
:return: The converted matrix
|
||||
"""
|
||||
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:
|
||||
width = max(width, int(math.log10(val)) + 1)
|
||||
|
||||
# Make the format string
|
||||
format = '%%%dd' % width
|
||||
|
||||
# Print the matrix
|
||||
for row in matrix:
|
||||
sep = '['
|
||||
for val in row:
|
||||
sys.stdout.write(sep + format % val)
|
||||
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)]
|
||||
|
||||
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
|
Loading…
Add table
Add a link
Reference in a new issue