Solution
Dans l'espace de toutes les lignes du graphique, les lignes qui passent par un coin sont exactement celles où le nombre d'intersections est sur le point de diminuer. En d'autres termes, elles forment chacune un maximum local.
Et pour toute ligne qui passe par au moins un coin, il existe une ligne associée qui passe par deux coins et qui a le même nombre d'intersections.
La conclusion est que nous devons seulement vérifier les lignes formées par deux coins de rectangle car elles forment un ensemble qui représente entièrement les maxima locaux de notre problème. Parmi celles-ci, nous choisissons celle qui a le plus d'intersections.
Complexité du temps
Cette solution doit d'abord récupérer toutes les lignes qui passent par deux coins. Le nombre de ces lignes est O(n^2) .
Nous devons ensuite compter le nombre d'intersections entre une ligne donnée et un rectangle. Cela peut évidemment être fait en O(n) en comparant à chacun des rectangles.
Il pourrait y avoir une manière plus efficace de procéder, mais nous savons que cet algorithme est alors au plus O(n^3) .
Mise en œuvre de Python3
Voici une implémentation Python de cet algorithme. Je l'ai orienté plus vers la lisibilité que l'efficacité, mais il fait exactement ce qui est défini ci-dessus.
def get_best_line(rectangles):
"""
Given a set of rectangles, return a line which intersects the most rectangles.
"""
# Recover all corners from all rectangles
corners = set()
for rectangle in rectangles:
corners |= set(rectangle.corners)
corners = list(corners)
# Recover all lines passing by two corners
lines = get_all_lines(corners)
# Return the one which has the highest number of intersections with rectangles
return max(
((line, count_intersections(rectangles, line)) for line in lines),
key=lambda x: x[1])
Cette implémentation utilise les aides suivantes.
def get_all_lines(points):
"""
Return a generator providing all lines generated
by a combination of two points out of 'points'
"""
for i in range(len(points)):
for j in range(i, len(points)):
yield Line(points[i], points[j])
def count_intersections(rectangles, line):
"""
Return the number of intersections with rectangles
"""
count = 0
for rectangle in rectangles:
if line in rectangle:
count += 1
return count
Et voici la définition des classes qui servent de structure de données pour les rectangles et les lignes.
import itertools
from decimal import Decimal
class Rectangle:
def __init__(self, x_range, y_range):
"""
a rectangle is defined as a range in x and a range in y.
By example, the rectangle (0, 0), (0, 1), (1, 0), (1, 1) is given by
Rectangle((0, 1), (0, 1))
"""
self.x_range = sorted(x_range)
self.y_range = sorted(y_range)
def __contains__(self, line):
"""
Return whether 'line' intersects the rectangle.
To do so we check if the line intersects one of the diagonals of the rectangle
"""
c1, c2, c3, c4 = self.corners
x1 = line.intersect(Line(c1, c4))
x2 = line.intersect(Line(c2, c3))
if x1 is True or x2 is True \
or x1 is not None and self.x_range[0] <= x1 <= self.x_range[1] \
or x2 is not None and self.x_range[0] <= x2 <= self.x_range[1]:
return True
else:
return False
@property
def corners(self):
"""Return the corners of the rectangle sorted in dictionary order"""
return sorted(itertools.product(self.x_range, self.y_range))
class Line:
def __init__(self, point1, point2):
"""A line is defined by two points in the graph"""
x1, y1 = Decimal(point1[0]), Decimal(point1[1])
x2, y2 = Decimal(point2[0]), Decimal(point2[1])
self.point1 = (x1, y1)
self.point2 = (x2, y2)
def __str__(self):
"""Allows to print the equation of the line"""
if self.slope == float('inf'):
return "y = {}".format(self.point1[0])
else:
return "y = {} * x + {}".format(round(self.slope, 2), round(self.origin, 2))
@property
def slope(self):
"""Return the slope of the line, returning inf if it is a vertical line"""
x1, y1, x2, y2 = *self.point1, *self.point2
return (y2 - y1) / (x2 - x1) if x1 != x2 else float('inf')
@property
def origin(self):
"""Return the origin of the line, returning None if it is a vertical line"""
x, y = self.point1
return y - x * self.slope if self.slope != float('inf') else None
def intersect(self, other):
"""
Checks if two lines intersect.
Case where they intersect: return the x coordinate of the intersection
Case where they do not intersect: return None
Case where they are superposed: return True
"""
if self.slope == other.slope:
if self.origin != other.origin:
return None
else:
return True
elif self.slope == float('inf'):
return self.point1[0]
elif other.slope == float('inf'):
return other.point1[0]
elif self.slope == 0:
return other.slope * self.origin + other.origin
elif other.slope == 0:
return self.slope * other.origin + self.origin
else:
return (other.origin - self.origin) / (self.slope - other.slope)
Exemple
Voici un exemple fonctionnel du code ci-dessus.
rectangles = [
Rectangle([0.5, 1], [0, 1]),
Rectangle([0, 1], [1, 2]),
Rectangle([0, 1], [2, 3]),
Rectangle([2, 4], [2, 3]),
]
# Which represents the following rectangles (not quite to scale)
#
# *
# *
#
# ** **
# ** **
#
# **
# **
Nous pouvons clairement voir qu'une solution optimale devrait trouver une ligne qui passe par trois rectangles et c'est effectivement ce qu'elle produit.
print('{} with {} intersections'.format(*get_best_line(rectangles)))
# prints: y = 0.50 * x + -5.00 with 3 intersections
0 votes
Pourquoi ne pas commencer à dessiner ces lignes de chaque coin de rectangle à chaque autre coin de rectangle et ensuite choisir le maximum ?
0 votes
@AndriyBerestovskyy comment savons-nous que la ligne passerait nécessairement par les coins de deux rectangles ?
1 votes
Pour que la programmation dynamique soit pertinente, vous devez formuler la question de telle sorte qu'elle puisse être divisée en sous-problèmes qui se chevauchent, et que les solutions optimales de ces sous-problèmes puissent être utilisées pour générer une solution optimale pour le problème dans son ensemble. Je ne sais pas si cela répond à cette exigence.
0 votes
@ on ne le fait pas, mais si on a besoin de trouver le nombre maximum d'intersections, le cas d'angle serait lorsque la ligne touche un angle, je suppose.
1 votes
@ si une ligne ne passe pas par deux coins, on peut toujours la faire bouger un peu sans changer le nombre d'intersections.
0 votes
@n.m. bon point.
0 votes
Je vais vous soumettre quelques idées : 1. Est-ce que l'exécution d'une ACP à travers les centres des cases vous donnerait une approximation assez décente ? 2. Que pensez-vous d'un algorithme génétique dont le centre et l'angle de la ligne sont les phénotypes et le nombre maximum de boîtes qu'elle croise est la règle de fitness (en commençant par le centre du groupe de boîtes et en tournant, la convergence serait plus rapide) ?
0 votes
Chaque rectangle étant composé de points simples, on peut ensuite ajuster un polynôme d'ordre 1 (une ligne) au nuage de points. Cela pourrait nous donner une ligne qui passe par le plus de points
0 votes
Vérifier l'idée de la procédure de vote dans Transformée de Hough .
0 votes
Vous pouvez simplifier le problème en divisant la zone en blocs et en comptant le nombre de rectangles dans chacun d'eux. La solution est une ligne qui traverse autant de zones à forte densité que possible. i.stack.imgur.com/lrDmC.png