-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathMetricCalculation.py
More file actions
273 lines (237 loc) · 10.8 KB
/
MetricCalculation.py
File metadata and controls
273 lines (237 loc) · 10.8 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
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
# -*- coding: utf-8 -*-
"""
Packing Santa's Sleigh -- Metric Calculation
@author: Joyce Noah-Vanhoucke
Created: Nov 22 09:55:38 2013
"""
import os
import csv
import math
import time
from collections import namedtuple
def int_reader_wrapper(reader):
""" Allows file contents to be read as ints not as strings. """
for row in reader:
yield map(int, row)
def readPresentsFile(presentsFilename):
""" Read file contents into memory as a dictionary of lists.
Key is presentID, Value is dimensions.
Arguments:
presentFilename: name of file containing present Ids and Dimensions
Returns:
Dictionary of lists.
"""
solution = {}
with open(presentsFilename, 'rb') as f:
f.readline() # header
fcsv = int_reader_wrapper(csv.reader(f))
for row in fcsv:
solution[row[0]] = row[1:]
return solution
def readSubmissionFile(submissionFilename):
""" Read file contents into memory as a dictionary of lists.
Key is PresentId, Values is 8 vertices of the packed present.
Arguments:
submissionFilename: name of file containing present Ids and packing locations
Returns:
Dictioary of lists
"""
submission = {}
with open(submissionFilename, 'rb') as f:
f.readline() # header
fcsv = int_reader_wrapper(csv.reader(f))
for row in fcsv:
submission[row[0]] = row[1:]
return submission
Vertex = namedtuple('Vertex', 'x y z')
class Present:
def create_vertices_list(self, submissionPresent):
NUMBER_VERTICES = 8
list_vertices = []
for i in xrange(NUMBER_VERTICES):
vertex = Vertex(submissionPresent[i*3], \
submissionPresent[i*3 + 1], \
submissionPresent[i*3 + 2])
list_vertices.append(vertex)
return list_vertices
def set_submitted_package_dimensions(self):
""" Returns the x, y, z values of the submitted package as a list. """
xvalues = list(set(self.packageVertices[i].x for i in xrange(len(self.packageVertices))))
yvalues = list(set(self.packageVertices[i].y for i in xrange(len(self.packageVertices))))
zvalues = list(set(self.packageVertices[i].z for i in xrange(len(self.packageVertices))))
if len(zvalues) != 2 or len(yvalues) != 2 or len(xvalues) != 2:
# valid package coordinates have exactly 6 values: 2 each for x, y, z
print 'Error with submitted package vertices'
exit()
else:
self.MinX = min(xvalues)
self.MaxX = max(xvalues)
self.MinY = min(yvalues)
self.MaxY = max(yvalues)
self.MinZ = min(zvalues)
self.MaxZ = max(zvalues)
return [int(math.fabs(xvalues[0] - xvalues[1]) + 1), \
int(math.fabs(yvalues[0] - yvalues[1]) + 1), \
int(math.fabs(zvalues[0] - zvalues[1]) + 1)]
def is_expected_dimensions(self):
# submitted packages may be rotated in any direction by 90-degrees
return ( set(self.expectedDimensions) == set(self.submittedPackageDimensions) )
def is_in_sleigh(self):
xvalues = list(set(self.packageVertices[i].x for i in xrange(len(self.packageVertices))))
yvalues = list(set(self.packageVertices[i].y for i in xrange(len(self.packageVertices))))
zvalues = list(set(self.packageVertices[i].z for i in xrange(len(self.packageVertices))))
return ( min(xvalues) > 0 and max(xvalues) <= self.SleightWidth and \
min(yvalues) > 0 and max(yvalues) <= self.SleightWidth and \
min(zvalues) > 0 )
def __init__(self, solution, submission, presentId):
self.SleightWidth = 1000
self.MinX = None
self.MaxX = None
self.MinY = None
self.MaxY = None
self.MinZ = None
self.MaxZ = None
self.Id = presentId
self.expectedDimensions = solution[presentId]
self.packageVertices = self.create_vertices_list(submission[presentId])
self.submittedPackageDimensions = self.set_submitted_package_dimensions()
if not (self.is_expected_dimensions()):
print 'Submitted package ' + str(self.Id) + ' is not of the expected dimension'
print 'Expected Dimensions = ' + str(self.expectedDimensions)
print 'Submitted Dimensions = ' + str(self.submittedPackageDimensions)
exit()
if not (self.is_in_sleigh()):
print 'Submitted package ' + str(self.Id) + ' is not in the sleigh'
print 'Package Vertices = ' + str(self.packageVertices)
exit()
def intersects_with_another_present(self, otherPresent):
""" Determines if present interects with another present in the xy-plane.
Arguments:
otherPresent: Present object
Returns:
boolean
"""
ans = True
if self.MaxX < otherPresent.MinX:
ans = False
if self.MinX > otherPresent.MaxX:
ans = False
if self.MaxY < otherPresent.MinY:
ans = False
if self.MinY > otherPresent.MaxY:
ans = False
if ans == True:
# check z
if ( (self.MinZ <= otherPresent.MaxZ and self.MaxZ > otherPresent.MinZ ) ):
print 'Collision detected between presents ' + str(self.Id) + ', ' + str(otherPresent.Id)
exit()
ans = False
return ans
def update_ordered_presents(orderedPresents, presentZHeight, presentId):
""" Updates the list ordered presents such that presents are
listed in the order they appear from top to bottom of the sleigh.
For presents that are found in the same horizontoal cross section,
they are listed in numerical order.
Arguments:
orderedPresents: list of present ids
Returns:
orderedPresents: dictionary of sets, key is z-height of top of present ids
in the set
"""
if presentZHeight not in orderedPresents:
orderedPresents[presentZHeight] = set()
orderedPresents[presentZHeight].add(presentId)
def GetOrderedPresentsStartingAtTop(solution, submission):
""" Goes through submission and returns an ordered list of present Ids
as they appear in the sleigh from top to bottom. For presents in the
same horizontal cross section (same xy-plane), the smallest present ids
appears first.
Arguments:
solution: Dictionary of lists. Key is present id, value is dimensions
submission: Dictionary of lists. Key is present id, value is list of Vertex
Returns:
presents: dictionary of present objects, key is present id
presentsInCrossSection: dictionary of sets, key is z-height
"""
presents = {}
orderedPresents = {}
for presentId in submission.keys():
presents[presentId] = Present(solution, submission, presentId)
update_ordered_presents(orderedPresents, presents[presentId].MaxZ, presentId)
return presents, orderedPresents
def remove_presents_above_zheight(currentPresentsSet, zheight, presents):
""" If zheight is lower than minimum z values of presents in currentPresentsSet,
the remove from currentPresentsSet.
Arguments:
currentPresentsSet: list of present ids of presents currently in zheight cross section
zheight: current position in z
presents: dictionary of Present objects, key is present id
Returns:
void (updated currentPresents)
"""
presentsToDiscard = set([i for i in currentPresentsSet if presents[i].MinZ > zheight])
currentPresentsSet.difference_update(presentsToDiscard)
def add_to_current_presents(currentPresentsSet, presents, presentToAdd):
""" Given set of current presents, check for collisions with presentToAdd.
If no intersection, then adds presentToAdd to currentPresentsSet
Arguments:
currentPresentsSet: set of current present ids at current z-height of sleigh
presents: dictionary of Present objects, key is present id
presentToAdd: present id to be added to currentPresentsSet
Returns:
void (updated currentPresentsSet)
"""
for pid in currentPresentsSet:
if presents[pid].intersects_with_another_present(presents[presentToAdd]):
print 'Collision found between presents ' + str(pid) + ', ' + str(presentToAdd)
exit()
currentPresentsSet.add(presentToAdd)
def update_current_presents(currentPresentsSet, zheight, presents, presentsInCrossSection):
""" Given the presents in the current horizontal cross section,
checks for present intersections and updates list currentPresents
based on min/max z-values.
Arguments:
currentPresentsSet: set of present ids for presents intersecting
current z-height
presents: dictionary of Present objects
presentsInCrossSection: list of present ids in current cross section
Returns:
void (updates currentPresentsSet)
"""
remove_presents_above_zheight(currentPresentsSet, zheight, presents)
for i in xrange(len(presentsInCrossSection)):
if len(currentPresentsSet) == 0:
currentPresentsSet.add(presentsInCrossSection[i])
else:
add_to_current_presents(currentPresentsSet, presents, presentsInCrossSection[i])
def getTotalVolume(solution):
""" Returns the total occupied volume of all the presents. """
volume = 0
for id in solution:
volume += solution[id][0] * solution[id][1] * solution[id][2]
return volume
if __name__ == "__main__":
start = time.clock()
path = 'xxxx'
presentsFilename = os.path.join(path, 'presents.csv')
submissionFilename = os.path.join(path, 'SubmissionFile.csv')
# read file contents into solution dictionary and submission dictionary
solution = readPresentsFile(presentsFilename)
submission = readSubmissionFile(submissionFilename)
print 'contents in memory'
# create presents objects, and their order going down sleigh
presents, orderedPresents = GetOrderedPresentsStartingAtTop(solution, submission)
orderTerm = 0
presentsSeenSoFar = 0
currentPresentsSet = set()
for zheight in sorted(orderedPresents.iterkeys(), reverse=True):
presentsInCrossSection = list(orderedPresents[zheight])
presentsInCrossSection.sort()
update_current_presents(currentPresentsSet, zheight, presents, presentsInCrossSection)
# calculate order term on the fly
for i in xrange(len(presentsInCrossSection)):
presentsSeenSoFar += 1
orderTerm += math.fabs(presentsSeenSoFar - presentsInCrossSection[i])
metric = 2 * max(orderedPresents.keys()) + orderTerm
print 'Metric = ' + str(metric)
print '\nTotal clock time = ' + str(time.clock() - start)