在我们的几何库中,我们有一个Polygon类,当然包含一个检查此多边形是否包含另一个多边形的方法:
public bool Contains(Polygon other) {
//complex containment checking code goes here
}
需要注意的其他重要事项是多边形是可变的,并且无法通知其形状何时发生变化。
现在,在我们的一个项目中,我们有一个验证函数,它检查大量的多边形是否全部包含在一个外边界内。
Polygon boundary = ...;
foreach (var poly in _myMassiveListOfPolygons)
if (!boundary.Contains(poly))
//error handling code goes here
剖析已经确定这是一个相当大的瓶颈。幸运的是,有一种方法可以大大加快计算速度。我们知道边界多边形总是凸的,所以我们可以编写一个假定外多边形是凸的专用方法,而不是使用通用的Contains方法。
最初我在客户端代码中添加了类似的东西:
Func<Polygon, bool> containsFunc;
// Keep both options, in case my assumption about being always convex is wrong
if (boundary.IsConvex())
{
containsFunc = p => { ... }; //efficient code for convex boundary goes here
}
else
{
containsFunc = p => boundary.Contains(p);
}
foreach (var poly in _myMassiveListOfPolygons)
if (!containsFunc(poly))
//error handling code goes here
四处欢乐!表现增加了十倍!
但是,此替代Contains方法的代码位于客户端项目中似乎不对,其他人无法重用它。
所以我尝试将它移动到Polygon类本身。
public bool Contains(Polygon other) {
if (this.IsConvex())
return ConvexContains(other);
else
return GenericContains(other);
}
private bool GenericContains(Polygon other) { ...}
private bool ConvexContains(Polygon other) { ...}
使用此方法,客户端代码将返回原始代码。不幸的是,与前面的代码相比有一个很大的区别:在有一次调用boundary.IsConvex()之前选择要使用的正确函数,现在在每次调用Contains时调用此方法!虽然这仍然比使用通用的Contains方法更快,但大多数性能改进都会再次丢失(更不用说凹面多边形的性能下降)。
第三种可能性是有两个公共方法来检查包含,其中一个假定多边形是凸的(当然不做检查本身,否则我们回到之前的开销问题)。这似乎不是一个好主意,因为用凹形多边形意外调用它可能会产生意想不到的结果,这使得很难找到错误。
所以最后,我的问题是,有什么好方法可以解决这种情况吗?我们应该在哪里放置替代包含方法,我们应该如何调用它?
修改
我喜欢缓存多边形是否凸出的想法。但我无法改变界面或类的行为,这在以下情况下会出现问题:
//relevant part of the polygon interface
public List<Point> Points { get; set; }
//client code
var poly = new Polygon();
var list = new List<Point>
{
new Point(0,0),
new Point(10,0),
new Point(0,10)
};
poly.Points = list;
list.Add(new Point(1, 1));
最后一行代码是在常规List<Point>
上执行的,我无能为力。但是,现在有2个要求:
答案 0 :(得分:2)
一种选择是在几何库中创建另一个概念,比如FixedBoundary
,它可以封装该逻辑。它将是不可变的,因此您可以安全地缓存凸性。一个例子:
public class FixedBoundary
{
private Polygon boundary;
private bool isConvex;
public FixedBoundary(Polygon boundary)
{
// deep clone so we don't have to worry about the boundary being modified later.
this.boundary = boundary.Clone();
this.isConvex = this.boundary.IsConvex();
}
public bool Contains(Polygon p)
{
if (isConvex)
{
// efficient convex logic here
}
else
{
return this.boundary.Contains(p);
}
}
}
这当然会为几何库增加一些概念上的重量。另一种选择是添加一个ImmutablePolygon(和扩展名为ImmutablePoint),它可以做同样的事情,但是转换可能很麻烦并影响性能。
答案 1 :(得分:1)
您可以使用Func<T, bool>
委托复制您已经完成的工作.. Polygon
内部..也许是这样的?
private Func<Polygon, bool> _containsFunc;
// ...
public bool Contains(Polygon other) {
if (_containsFunc == null) {
if (this.IsConvex())
_containsFunc = ConvexContains;
else
_containsFunc = GenericContains;
}
return _containsFunc(other);
}
第一次拨打Contains
后的每次通话都不会拨打IsConvex
。除非我误解了你......这听起来像是你所追求的。
答案 2 :(得分:0)
好吧......你们在公共界面中使用List,真的开枪了。以下是您可以尝试解决它的方法(有一个小的非零机会,这将错误地缓存,但它是1英寸(1 <&lt; 32)的机会。
正如您所注意到的,我们可以缓存多边形的凸面...但问题就变成了缓存失效的情况。
无法在对象更改时无效缓存。您必须在每次调用时检查缓存一致性。这就是哈希的用武之地。
默认情况下,列表的哈希很无用,你想使用this solution for getting a usable hash。
所以你想要做的是在你的多边形上添加nullable<int> _cachedHashCode
。当您致电Polygon.IsConvex
时,您会将List<Point>
哈希,与_cachedHashCode
进行比较,如果它们不相等,请重新计算您的IsConvex。