-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d6aeb5d
Showing
3 changed files
with
403 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
import sys | ||
from typing import NewType, Sequence, Tuple | ||
Matrix = NewType('Matrix', Sequence[Sequence[int]]) | ||
|
||
class Hungarian: | ||
|
||
def __init__(self): | ||
self.mat = None | ||
self.row_covered = [] | ||
self.col_covered = [] | ||
self.starred = None | ||
self.n = 0 | ||
self.Z0_r = 0 | ||
self.Z0_c = 0 | ||
self.series = None | ||
|
||
def solve(self, cost_matrix: Matrix): | ||
self.mat = cost_matrix | ||
self.n = len(self.mat) | ||
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.series = [[0 for j in range(2)] for j in range(self.n*2)] | ||
self.starred = [[0 for j in range(self.n)] for j in range(self.n)] | ||
|
||
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: | ||
done = True | ||
|
||
results = [] | ||
for i in range(self.n): | ||
for j in range(self.n): | ||
if self.starred[i][j] == 1: | ||
results += [(i, j)] | ||
|
||
return results | ||
|
||
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. | ||
""" | ||
n = self.n | ||
for i in range(n): | ||
vals = [x for x in self.mat[i]] | ||
minval = min(vals) | ||
for j in range(n): | ||
self.mat[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. | ||
""" | ||
for i in range(self.n): | ||
for j in range(self.n): | ||
if (self.mat[i][j] == 0) and \ | ||
(not self.col_covered[j]) and \ | ||
(not self.row_covered[i]): | ||
self.starred[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.starred[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.starred[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 | ||
series = self.series | ||
series[count][0] = self.Z0_r | ||
series[count][1] = self.Z0_c | ||
done = False | ||
while not done: | ||
row = self.__find_star_in_col(series[count][1]) | ||
if row >= 0: | ||
count += 1 | ||
series[count][0] = row | ||
series[count][1] = series[count-1][1] | ||
else: | ||
done = True | ||
|
||
if not done: | ||
col = self.__find_prime_in_row(series[count][0]) | ||
count += 1 | ||
series[count][0] = series[count-1][0] | ||
series[count][1] = col | ||
|
||
self.__convert_series(series, 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.mat[i][j] += minval | ||
if not self.col_covered[j]: | ||
self.mat[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.mat[i][j]: | ||
minval = self.mat[i][j] | ||
return minval | ||
|
||
def __find_a_zero(self, i0: int = 0, j0: int = 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.mat[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: Sequence[int]): | ||
""" | ||
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.starred[row][j] == 1: | ||
col = j | ||
break | ||
|
||
return col | ||
|
||
def __find_star_in_col(self, col: Sequence[int]): | ||
""" | ||
Find the first starred element in the specified col. Returns | ||
the row index, or -1 if no starred element was found. | ||
""" | ||
row = -1 | ||
for i in range(self.n): | ||
if self.starred[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.starred[row][j] == 2: | ||
col = j | ||
break | ||
|
||
return col | ||
|
||
|
||
def __convert_series(self, | ||
series: Matrix, | ||
count: int): | ||
""" | ||
Unstar each starred zero | ||
of the series, star each primed zero of the series | ||
""" | ||
for i in range(count+1): | ||
if self.starred[series[i][0]][series[i][1]] == 1: | ||
self.starred[series[i][0]][series[i][1]] = 0 | ||
else: | ||
self.starred[series[i][0]][series[i][1]] = 1 | ||
|
||
|
||
def __clear_covers(self): | ||
for i in range(self.n): | ||
self.row_covered[i] = False | ||
self.col_covered[i] = False | ||
|
||
|
||
def __erase_primes(self): | ||
for i in range(self.n): | ||
for j in range(self.n): | ||
if self.starred[i][j] == 2: | ||
self.starred[i][j] = 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import tkinter as tk | ||
from tkinter import ttk | ||
import hungarian_method as hm | ||
from matrix_input import * | ||
|
||
|
||
class main(tk.Frame): | ||
def __init__(self, parent): | ||
tk.Frame.__init__(self, parent) | ||
self.win1 = tk.Toplevel() | ||
self.win1.geometry("350x50") | ||
self.win1.attributes('-topmost', 'true') | ||
btn = ttk.Button(self.win1, text="Create", command=self.win1_submit) | ||
self.e1 = tk.Entry(self.win1, width = 5) | ||
self.lbl = tk.Label(self.win1, text = "Enter the size of the matrix: ") | ||
self.lbl.grid(row=1, column=1) | ||
self.e1.grid(row=1, column=2) | ||
btn.grid(row=1, column=3, padx=10, pady=2) | ||
|
||
|
||
def win1_submit(self): | ||
self.table = MatrixInput(self, int(self.e1.get()), int(self.e1.get())) | ||
self.solve = ttk.Button(self, text="Solve", width=6, command=self.on_submit) | ||
self.parser = ttk.Button(self, text="Parser", width=6, command=self.on_parser) | ||
self.resultLabel = tk.Label(self, text = "", font=("Helvetica", 13), padx=30) | ||
self.table.grid(row=1, column=1, padx=30, pady=20) | ||
self.solve.grid(row=1, column= 2) | ||
self.parser.grid(row=1, column= 3, padx=20) | ||
self.resultLabel.grid(row=5,column= 1) | ||
self.grid_columnconfigure(index=0, weight=1) | ||
self.grid_rowconfigure(index=2, weight=1) | ||
self.win1.destroy() | ||
|
||
def on_submit(self): | ||
solver = hm.Hungarian() | ||
sol = solver.solve(self.table.get()) | ||
self.resultLabel["text"] = 'The Assignment is: \n' | ||
for i in range(solver.n): | ||
self.resultLabel["text"] += ("Assignee #"+str(sol[i][0]+1)+" --> Task #"+str(sol[i][1]+1)+"\n") | ||
|
||
|
||
def on_parser(self): | ||
self.win2 = tk.Toplevel() | ||
self.win2.attributes('-topmost', 'true') | ||
self.tb = tk.Text(self.win2, height=10, width=30) | ||
self.parse = ttk.Button(self.win2, text="Parse", width=5, command=self.on_parse) | ||
self.tb.grid(row=1, column=1, padx=30, pady=30) | ||
self.parse.grid(row=1, column=2, padx=30) | ||
self.win2.grid_columnconfigure(index=0, weight=1) | ||
self.win2.grid_rowconfigure(index=2, weight=1) | ||
|
||
|
||
def on_parse(self): | ||
matStr = self.tb.get("1.0",'end-1c').split("--") | ||
row = 0 | ||
col = 0 | ||
for j in range(len(matStr)): | ||
elements = matStr[j].split("-") | ||
for i in elements: | ||
self.table._entry[(row, col)].insert(0, str(i)) | ||
col =col+1 | ||
row = row + 1 | ||
col = 0 | ||
|
||
|
||
root = tk.Tk() | ||
root.title("Hungarian Method") | ||
#root.geometry("400x400") | ||
main(root).pack(side="top", fill="both", expand=True) | ||
root.mainloop() |
Oops, something went wrong.