-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathassignmentProb.py
388 lines (328 loc) · 12.8 KB
/
assignmentProb.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
"""Solve Assignment Problem
Subject: Operations Research
Author: Aman Garg
Date: 07/11/2016
"""
from itertools import combinations
from collections import deque
from sys import maxint, argv, exit
_GUI_ROW, _GUI_COL, _GUI_M = 0, 0, None
_backup = None
# Delimit the output of the program by a hyphen so that the GUI program
# can separate the values. Give provision to store the final results
delimiter = '\n-'
finalResult = None
class HorzLine:
"""Denotes a horizontal line across the matrix"""
def __init__(self, pos):
self.finalResult = None
self.pos = pos
self.type = "Horizontal"
self.across = "row"
def __repr__(self):
return 'H%d' % (self.pos)
class VertLine:
"""Denotes a vertical line across the matrix"""
def __init__(self, pos):
self.pos = pos
self.type = "Vertical"
self.across = "column"
def __repr__(self):
return 'V%d' % (self.pos)
class HungarianAssignment:
"""A class that solves the HungarianAssignment problem"""
def __init__(self):
self.row, self.col, self.M = 0, 0, None
self.Z = None
def printMatrix(self):
print
for i in self.M:
for j in i:
print ' ', j, ' ',
print
print delimiter
def reduceMatrix(self):
"""Returns a row and column reduced matrix"""
for i in xrange(self.row):
minElem = min(self.M[i])
self.M[i] = map(lambda x: x - minElem, self.M[i])
# Now for column reduction
for col in xrange(self.row):
l = []
for row in xrange(self.row):
l.append(self.M[row][col])
minElem = min(l)
for row in xrange(self.row):
self.M[row][col] -= minElem
def getZeroPositions(self):
"""Returns current positions of 0's"""
self.Z = set()
for i in xrange(self.row):
for j in xrange(self.row):
if self.M[i][j] == 0:
self.Z.add((i, j))
def printZeroLocations(self):
print '\n Zeros are located at follows:\n\n',
for i in self.Z:
print i
print delimiter
def checkAssignments(self):
"""What is the minimum assignment possible to cover all zeros"""
global _backup
bestComb = self.getSetOfCrossingLines()
len_BC = len(bestComb)
print '\n Current best combination covering all zeros: %d\n' % (len_BC)
for i in bestComb:
print '\t%s line through %s : %d\n' % (i.type, i.across, i.pos)
print delimiter
curAssignments, totalVal = self.getAssignment(), 0
print '\n The assignments are as follows: \n\n',
for i in curAssignments:
x, y = i[0], i[1]
print '\t At: ', x, y, ' Value: ', _backup[x][y], '\n'
totalVal += _backup[x][y]
if len(bestComb) != self.row:
# Perform the following steps
print '\n Current solution isn\'t optimal: lines are not enough\n'
print delimiter
self.tickRowsAndColumns(curAssignments)
else:
self.finalResult = '\n Optimal assignments are as follows: \n\n'
print '\n Current solution is optimal: Minimal cost: ', totalVal
print delimiter
print '\n Final assignments are as follows: \n\n',
for i in curAssignments:
x, y = i[0], i[1]
print '\t At: ', x, y, ' Value: ', _backup[x][y], '\n'
self.finalResult += '\t At: %d %d \tValue: %d\n\n' % (
x, y, _backup[x][y])
self.finalResult += '\n Minimum cost incurred: %d \n' % (totalVal)
return
def getDummy(self, n, m):
"""Add a dummy variable when rows != columns"""
_m = max(n, m)
print self.M
for i in xrange(_m):
for j in xrange(_m):
self.M[i][j] = 0 if self.M[i][j] == -1 else self.M[i][j]
return self.M
def getSetOfCrossingLines(self):
"""Returns a set of lines that minimally cover all zeros"""
horzLines = [HorzLine(i) for i in xrange(self.row)]
vertLines = [VertLine(i) for i in xrange(self.row)]
# We have to choose maximum n lines for crossing out all zeros
# The assignment is optimal when the minimal lines are the order of the
# matrix
allComb, bestComb = [], []
for i in xrange(1, self.row + 1):
allComb.extend(combinations(horzLines + vertLines, i))
# Find the combination which covers the lists in the minimumLines
for i in allComb:
covered = set()
for j in i:
for zero in self.Z:
if zero in covered:
continue
elif j.type == 'Horizontal' and j.pos == zero[0]:
covered.add(zero)
elif j.type == 'Vertical' and j.pos == zero[1]:
covered.add(zero)
if len(covered) == len(self.Z):
if bestComb == []:
bestComb = i
elif len(i) < len(bestComb):
bestComb = i
return bestComb
def getAssignment(self):
"""Asssign maximum number of zeroes possible """
removedSet = set()
# As there can be max n zeros
bestAssign = set()
# Since there are atleast 4 zeroes in our zeroes, array
for comb in combinations(self.Z, self.row):
removedSet = set()
totalSet = set(comb)
curAssign = set()
for j in totalSet:
if j in removedSet:
continue
r, c = j[0], j[1]
# remove others has same row/col
curAssign.add(j)
for k in totalSet:
if k != j and k not in removedSet:
if k[0] == r or k[1] == c:
removedSet.add(k)
if len(curAssign) > len(bestAssign):
bestAssign = curAssign.copy()
return bestAssign
def tickRowsAndColumns(self, assignments):
"""Tick rows and columns in the Matrix accordingly:
- Tick rows that do not have an assignment
- Tick cols that have 0's in the marked row
- Tick all rows that have assignments in the marked column
- Repeat the above procedure till no more can be ticked
QuickThink: Use BFS and sets for row/cols
"""
global _backup
tickRows, tickCols = set(xrange(self.row)), set()
# Tick rows without assignment
for i in assignments:
curRow = i[0]
if curRow in tickRows:
tickRows.remove(curRow)
queue = deque(tickRows)
while queue:
# Tick cols that have 0's in ticked row
queue.popleft()
for row in tickRows:
for col in xrange(self.row):
if self.M[row][col] == 0:
tickCols.add(col)
for col in tickCols:
for assign in assignments:
if assign[1] == col and assign[0] not in tickRows:
tickRows.add(assign[0])
queue.append(assign[0])
print '\n Ticked rows: ', list(tickRows)
print ' Ticked cols: ', list(tickCols)
# Draw straight lines through unmarked rows and marked columns
horLines = [HorzLine(i) for i in xrange(self.row) if i not in tickRows]
verLines = [VertLine(i) for i in xrange(self.row) if i in tickCols]
bestComb = horLines + verLines
print '\n Marking unmarked rows & marked cols: ', len(bestComb), '\n'
for i in bestComb:
print '\t%s line through %s : %d' % (i.type, i.across, i.pos)
print delimiter
if horLines + verLines == self.row:
print '\n Current solution is optimal\n'
curAssignments, totalVal = self.getAssignment(), 0
print '\n The assignments are as follows: \n\n',
self.finalResult = '\n Optimal assignments are as follows: \n\n'
for i in curAssignments:
x, y = i[0], i[1]
print '\t At: ', x, y, ' Value: ', _backup[x][y], '\n'
self.finalResult += '\t At: %d %d \tValue: %d\n\n' % (
x, y, _backup[x][y])
totalVal += _backup[x][y]
self.finalResult += '\n\n Minimum cost incurred: %d\n ' % (
totalVal)
print delimiter
return True
else:
print '\n Current solution isn\'t optimal : lines aren\'t enough\n'
print ' Now going for uncovering elements pass\n\n'
self.smallestElements(bestComb)
self.getZeroPositions()
self.printZeroLocations()
self.checkAssignments()
def smallestElements(self, bestComb):
"""
Examine uncovered elements: Select min uncovered and subtract from all
uncovered elements. For elements at intersection of two lines,
add the min element, for rest : as it is
"""
H_MASK, V_MASK, I_MASK = "H", "V", "I"
MASK = [[None for i in xrange(self.row)] for j in xrange(self.row)]
for line in bestComb:
if line.type == "Horizontal":
row = line.pos
for col in xrange(self.row):
if MASK[row][col] is None:
MASK[row][col] = H_MASK
elif MASK[row][col] == V_MASK:
MASK[row][col] = I_MASK
elif line.type == "Vertical":
col = line.pos
for row in xrange(self.row):
if MASK[row][col] is None:
MASK[row][col] = V_MASK
elif MASK[row][col] == H_MASK:
MASK[row][col] = I_MASK
minElem = maxint
for i in xrange(self.row):
for j in xrange(self.row):
if MASK[i][j] == None:
minElem = min(minElem, self.M[i][j])
# Subtract min from uncovered, and add to intersection elems
for i in xrange(self.row):
for j in xrange(self.row):
if MASK[i][j] == None:
self.M[i][j] -= minElem
elif MASK[i][j] == I_MASK:
self.M[i][j] += minElem
print '\n Uncovered matrix\n',
self.printMatrix()
def readInput():
"""Read matrix from file and return a HungarianAssignment object"""
solver = HungarianAssignment()
if len(argv) != 2:
print '\n No input file feeded'
print ' Usage: python assignment.py "name_of_InputFile"'
return solver
try:
inputFile = argv[1]
f = file(inputFile, "r")
n, m = map(int, f.readline().strip().split(" "))
_m = max(n, m)
M = [[-1 for a in xrange(_m)]
for b in xrange(_m)] # denotes the matrix
for ind, i in enumerate(f.readlines()):
for indj, j in enumerate(map(int, i.strip().split(" "))):
M[ind][indj] = j
solver.M = M
if n != m:
print '\n Matrices aren\'t of the same order'
print ' Adding dummy\n'
print delimiter
solver.getDummy(n, m)
else:
print '\n No dummy required'
n = _m
solver.row, solver.col = n, m
except Exception as e:
print '\n Exception occured: %s Check again' % (e)
finally:
return solver
def fillFromGUI():
"""Creates Hungarian instance problem using GUI filled matrix"""
solver = HungarianAssignment()
n, m = _GUI_ROW, _GUI_COL
# solver.M = _GUI_M
_m = max(n, m)
solver.M = [[-1 for a in xrange(_m)]
for b in xrange(_m)] # denotes the matrix
if n != m:
print '\n Matrices aren\'t of the same order\n'
print ' Adding dummy\n'
for i in xrange(_m):
for j in xrange(_m):
try:
solver.M[i][j] = _GUI_M[i][j]
except IndexError:
solver.M[i][j] = 0
solver.row = solver.col = max(n, m)
return solver
def main(fileHandle=None):
global _backup, finalResult
# Obtain matrix from file
# or use matrix already filled by the GUI
solver = fillFromGUI() if _GUI_M else readInput()
if solver.M is None:
print ' Error occured during execution\n'
exit()
_backup = solver.M[:]
print '\n Received Matrix: \n',
solver.printMatrix()
# Reduce the matrix
solver.reduceMatrix()
print '\n Reduced Matrix: \n',
solver.printMatrix()
# Get zero positions from the array
solver.getZeroPositions()
solver.printZeroLocations()
# Check assignments
solver.checkAssignments()
finalResult = solver.finalResult
if __name__ == '__main__':
main()