-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtenants.py
More file actions
executable file
·209 lines (186 loc) · 6.77 KB
/
tenants.py
File metadata and controls
executable file
·209 lines (186 loc) · 6.77 KB
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
#!/usr/bin/python3
import sys
def tenant_matching2(possible_tenants, mins=None, exclude=None):
"""each apartment has some list of possible tenants
can every apartment find a tenant?
smart solution
"""
assert None, "lol; i couldn't make it work, too hard"
# setup
if mins is None:
exclude = set() if exclude is None else exclude
else:
exclude = {} if exclude is None else exclude
solution = {}
# select pawn
if possible_tenants:
col = next(iter(possible_tenants))
else:
return {}
tenants = set(possible_tenants[col]) # tenant=col, exclude is used columns
if mins is not None: # this is the part that probably doesn't scale
diffs = mins[col] # column difference
# sort by distance between pawn's column and proposed matching column
offby = -1
# exclude all columns which are already matched to closer pawns
last = None
for tenant in diffs: # (maybe) inefficient part 1
if tenant not in exclude:
continue
diff = diffs[tenant] - exclude[tenant]
if diff >= 0:
offby = max(diff, offby)
last = tenant if offby == diff else last
tenants -= {tenant}
else:
return None
# every pawn must have a column, even if all columns are matched to closer pawns
if not tenants:
return None
# sort by distance between pawn's column and proposed matching column
sl2 = sorted(list(tenants), key=lambda tenant: diffs[tenant])
# measure the maximum difference by which the previous pawns match better
# if this gain is less than the loss we obtain by using the next best remaining column for this pawn
# then it doesn't make sense to consider the previous pawns as well-matched
# example:
# r = {0: {0, 4}, 1:{0, 4}}
# m = {0: {0:3, 4:1}, 1:{4:2, 0:6}}
# m = {0: {0:3, 4:1}, 1:{4:2, 0:4}}
# without this check, we obtain the wrong solution: {0: 4, 1: 0}
# instead of the correct solution: {0: 0, 1: 4}
# NOT CORRECT
if diffs[sl2[0]] > offby and offby > 0:
return None
else:
# exclude columns which are already matched
tenants = tenants-exclude
sl2 = sorted(list(tenants))
# treat current pawn as matched, match the rest
passd = dict(possible_tenants)
del passd[col]
rest = None
idx = 0
while rest is None and idx < len(tenants):
# match the current pawn
if mins is None:
skip = set(exclude)
else:
skip = dict(exclude)
try:
if col == 1:
print("pawn", col, "sol", sl2, "idx", idx)
solution[col] = sl2[idx]
except IndexError:
print(sl2)
print(solution)
raise
if mins is None:
# add current matched column to list of skipped columns
skip.add(sl2[idx])
else:
# inform the rest how good this match is
if idx == len(tenants) - 1:
skip[sl2[idx]] = -1*float('inf') # last solution is forced to accept
else:
skip[sl2[idx]] = diffs[sl2[idx]]
# get the rest of the solution
rest = tenant_matching(passd, exclude=skip, mins=mins)
# if the solution does not exist,
# try the next closest non-skipped column
idx += 1
if rest is None:
# rest of the solution does not exist
return None
# concatenate the already matched pawns with the rest
solution.update(rest)
#break
# check to make sure columns are only used once
collision_chk(solution)
return solution
def collision_chk(solution):
rset = set()
for val in solution.values():
try:
assert val not in rset
except TypeError:
val = str(val)
assert val not in rset
rset.add(val)
def tenant_matching_solutions(mins, used=None):
"""get all solutions to get pawns home"""
if used is None:
used = set()
if not mins:
return {}
pawn = sorted(list(mins))[0]
solution = {}
sold = {}
count = 0
for col in mins[pawn]:
if col in used:
continue
solution[pawn] = col
dcopy = dict(mins)
del dcopy[pawn]
used2 = set(used)
used2.add(col)
if dcopy:
rest = tenant_matching_solutions(dcopy, used=used2)
for key in rest:
item = rest[key]
solution2 = dict(solution)
solution2.update(item)
collision_chk(solution2)
key2 = str(solution2)
if key2 not in sold:
sold[key2] = solution2
else:
count += 1
solution2 = dict(solution)
key = str(solution2)
collision_chk(solution2)
sold[key] = solution2
if count:
assert count == len(sold)
collision_chk(sold)
return sold
def tenant_matching(mins):
"""figure out which solution needs
the smallest amount of column movement"""
llen = len(mins)
sols = tenant_matching_solutions(mins)
total_sol = float('inf')
solution = {}
for sol in sols.values():
if len(sol) < llen:
continue
total = 0
for pawn in sol:
total += mins[pawn][sol[pawn]]
total_sol = min(total, total_sol)
solution = sol if total == total_sol else solution
return solution
if __name__ == '__main__':
#a=tenant_matching({0:set([3,1,2]), 1:set([3,1]), 2:set([2,3]), 3:set([1,0])})
#print(a)
r = {0: {1}, 1: {5, 6, 7}, 2: {1, 2, 3, 4, 5}, 3: {1, 2, 3}} # {0: 1, 1: 5, 2: 2, 3: 3}
m = {0: {1: 1}, 1: {5: 2, 6: 1, 7: 0}, 2: {1: 0, 2: 1, 3: 2, 4: 3, 5: 4}, 3: {1: 1, 2: 0, 3: 1}}
r = {0: {3, 4}, 1: {0, 3, 4, 5, 6, 7}, 2: {3, 4, 5, 6, 7}, 3: {4, 5, 6, 7}, 4: {0, 3, 4, 5}, 5: {3, 4, 5, 6, 7}}
m = {0: {3: 0, 4: 1}, 1: {0: 3, 3: 0, 4: 1, 5: 2, 6: 3, 7: 4}, 2: {3: 3, 4: 2, 5: 1, 6: 0, 7: 1}, 3: {4: 2, 5: 1, 6: 0, 7: 1}, 4: {0: 0, 3: 3, 4: 4, 5: 5}, 5: {3: 2, 4: 1, 5: 0, 6: 1, 7: 2}}
r = {0: {0, 4}, 1:{0, 4}}
m = {0: {0:3, 4:1}, 1:{4:2, 0:6}}
#solution = tenant_matching(r, mins=m)
m = {0: {1: 1}, 1: {5: 2, 6: 1, 7: 0}, 2: {1: 0, 2: 1, 3: 2, 4: 3, 5: 4}, 3: {1: 1, 2: 0, 3: 1}}
# solution count:
# 5, {2,3,4}, {{1,3}, {1,2}, {1,2,3}} # 7
# 6, {2,3,4,5} {
# 7
solution = tenant_matching(m)
print(solution)
min_off_board = 0
for pawn in solution:
print("p, diff", pawn, m[pawn][solution[pawn]])
min_off_board += m[pawn][solution[pawn]]
print(min_off_board)
# actual: {0:1, 1:5, 2:2, 3:3}
#print(a) # prints {0: 1, 1: 3, 2: 2, 3: 0}