patternpythonMinor
Averaging a list of intersecting rectangles
Viewed 0 times
averaginglistintersectingrectangles
Problem
I have a list of rectangles as tuples (x, y, w, h). I need to find which ones intersect and if they do, average all the intersecting rectangles into a new rectangle. I also track the resultant averaged rectangle "weight" (the number of intersecting rectangles) as an addition member in the new tuple. (x, y, w, h, weight)
The final return is resultant list of averaged sorted by weight.
I am looking for feedback on how to improve performance and make it even more pythonic. Or, as I have discovered many times in python, there may a completely different and better way to approach the problem (such as with NumPy).
I pass a list of
Here is my unit test to help understand how the code is being used:
```
def test_ordered_average_intersecting_rects(self):
# Arrange
rects = [(150, 55, 40, 20, 0),
(160, 40, 20, 20, 0),
(100, 120, 20, 20, 0),
(45, 10, 15, 30, 0),
The final return is resultant list of averaged sorted by weight.
I am looking for feedback on how to improve performance and make it even more pythonic. Or, as I have discovered many times in python, there may a completely different and better way to approach the problem (such as with NumPy).
I pass a list of
rect tuples to ordered_average_intersecting_rects:import numpy as np
def rects_intersect(r1, r2):
hoverlaps = (r1[0] = r2[0])
voverlaps = (r1[1] = r2[1])
return hoverlaps and voverlaps
def average_rects(rects):
x = np.average([coord[0] for coord in rects])
y = np.average([coord[1] for coord in rects])
w = np.average([coord[2] for coord in rects])
h = np.average([coord[3] for coord in rects])
return x, y, w, h, len(rects)
def ordered_average_intersecting_rects(rects):
intersecting_rect_clusters = []
while rects: # modified from `while len(rect) > 0:` (janos feedback)
r1 = rects[0]
rect_cluster = [r1]
for r2 in rects[1:]:
if rects_intersect(r1, r2):
rect_cluster.append(r2)
rects.remove(r2)
rects.remove(r1)
intersecting_rect_clusters.append(rect_cluster)
averaged_rects = [average_rects(cluster) for cluster in intersecting_rect_clusters]
averaged_rects.sort(key=lambda w: w[4], reverse=True)
return averaged_rectsHere is my unit test to help understand how the code is being used:
```
def test_ordered_average_intersecting_rects(self):
# Arrange
rects = [(150, 55, 40, 20, 0),
(160, 40, 20, 20, 0),
(100, 120, 20, 20, 0),
(45, 10, 15, 30, 0),
Solution
To improve performance, wait for another review with some clever
To make it more Pythonic, it would be better to represent a rectangle with a
The
The averaging method becomes:
The main part of
And in your testing method you can create
And of course you can (and should) go further:
You can replace
numpy trick.To make it more Pythonic, it would be better to represent a rectangle with a
Rectangle class instead of a simple tuple, for example:class Rectangle(object):
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def intersect(self, other):
hoverlaps = (self.x = other.x)
voverlaps = (self.y = other.y)
return hoverlaps and voverlapsThe
intersect method fits naturally into it, and now the code is easier to read.The averaging method becomes:
def average_rects(rects):
x = np.average([rect.x for rect in rects])
y = np.average([rect.y for rect in rects])
w = np.average([rect.width for rect in rects])
h = np.average([rect.height for rect in rects])
return x, y, w, h, len(rects)The main part of
ordered_average_intersecting_rects becomes:while rects:
r1 = rects[0]
rect_cluster = [r1]
for r2 in rects[1:]:
if r1.intersect(r2):
rect_cluster.append(r2)
rects.remove(r2)And in your testing method you can create
rects with:rects = [Rectangle(150, 55, 40, 20),
Rectangle(160, 40, 20, 20),
Rectangle(100, 120, 20, 20),
Rectangle(45, 10, 15, 30),
Rectangle(50, 25, 25, 20),
Rectangle(35, 35, 20, 30)]And of course you can (and should) go further:
average_rectscould return a tuple:Rectangle(x, y, w, h), len(rects)
- The assertions could use
cluster1.x,.y,.width, instead of cryptic[0],[1], ...
You can replace
while len(rects) > 0: with simply while rects:, because in Python empty sequences are false. This is recommended on PEP8 too.Code Snippets
class Rectangle(object):
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def intersect(self, other):
hoverlaps = (self.x <= other.x + other.width) and (self.x + self.width >= other.x)
voverlaps = (self.y <= other.y + other.height) and (self.y + self.height >= other.y)
return hoverlaps and voverlapsdef average_rects(rects):
x = np.average([rect.x for rect in rects])
y = np.average([rect.y for rect in rects])
w = np.average([rect.width for rect in rects])
h = np.average([rect.height for rect in rects])
return x, y, w, h, len(rects)while rects:
r1 = rects[0]
rect_cluster = [r1]
for r2 in rects[1:]:
if r1.intersect(r2):
rect_cluster.append(r2)
rects.remove(r2)rects = [Rectangle(150, 55, 40, 20),
Rectangle(160, 40, 20, 20),
Rectangle(100, 120, 20, 20),
Rectangle(45, 10, 15, 30),
Rectangle(50, 25, 25, 20),
Rectangle(35, 35, 20, 30)]Context
StackExchange Code Review Q#62159, answer score: 2
Revisions (0)
No revisions yet.