1

In our geometry library, we have a Polygon class, containing (amongst many others, of course) a method which checks if this polygon contains another polygon:

public bool Contains(Polygon other) {
    //complex containment checking code goes here
}

Other important things to note are that the polygon is mutable, and that there is no way to be notified of when its shape changes.

Now, in one of our projects, we have a validation function, which checks for a huge number of polygons whether they are all contained within a single outer boundary.

Polygon boundary = ...;
foreach (var poly in _myMassiveListOfPolygons)
    if (!boundary.Contains(poly))
        //error handling code goes here

Profiling has determined this to be quite a bottleneck. Fortunately there is a way to vastly speed up the calculation. We know that the boundary polygon is always convex, so instead of using the general Contains method, we can write a specialized method that assumes the outer polygon is convex.

Initially I added something like this to the client code:

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

Joy all around! Performance has increased tenfold!

However, it doesn't seem right that the code for this alternative Contains method is located in the client project, where it can't be reused by others.

So I tried to move it to the Polygon class itself.

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) { ...} 

With this method, the client code returns back to the original. Unfortunately, there is one big difference compared to the previous code: Before there was one call to boundary.IsConvex() to select the correct function to use, where now this method is called for every invocation of Contains! Although this is still faster than using the generic Contains method, most of the performance improvements are lost again (not to mention performance decreases for concave polygons).

A third possibility would be to have two public methods to check containment, where one of them assumes that the polygon is convex (without doing the check itself, of course, otherwise we're back to the previous overhead problem). This doesn't seem like a good idea, since calling it by accident with a concave polygon could give unexpected results, making for hard to find bugs.

So finally, my question is, is there any good way to deal with this situation? Where should we put the alternative Contains method, and how should we call it?

Edit

I like the idea of caching whether or not a polygon is convex. But I cannot change the interface nor the behavior of the class, which is problematic in a case such as this:

//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));

This last line of code is executed on a regular List<Point>, there is nothing I can do about that. However, there would now be 2 requirements:

  1. The polygon must now contain the added point. This is to ensure existing code doesn't break. This means that we cannot copy the Points into our own, observable list.
  2. We must get notified of this change to the list of points (because our polygon has turned from convex to concave). This means that we cannot wrap the list by our own, observable list, because only changes made through the wrapper would cause a notification.
Steven
  • 2,437
  • 5
  • 32
  • 36
  • [Perfect example of why you should never expose List](http://stackoverflow.com/questions/387937/why-is-it-considered-bad-to-expose-listt) – Aron Apr 04 '14 at 04:20
  • @Aron I'm well aware of that, but this library has been around longer than I have been graduated... – Steven Apr 04 '14 at 04:23
  • Wait on my answer...you'll like it :) – Aron Apr 04 '14 at 04:24

3 Answers3

2

One option is to create another concept in the geometry library, say FixedBoundary, which could encapsulate that logic. It would be immutable so that you could safely cache the convexity. An example:

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);
        }
    }
}

This of course adds some conceptual weight to the geometry library. Another option would be to add an ImmutablePolygon (and by extension ImmutablePoint) which could do the same, however conversions may be cumbersome and affect performance as well.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • I like this. All existing client code just keeps using the same old Polygon, and where sensible we can convert it to an immutable one with more efficient operations. This could well be worth the additional weight. – Steven Apr 04 '14 at 04:48
1

You could replicate what you've done already with the Func<T, bool> delegate.. internal to the Polygon.. perhaps like this?

private Func<Polygon, bool> _containsFunc;

// ...

public bool Contains(Polygon other) {
    if (_containsFunc == null) {
       if (this.IsConvex())
           _containsFunc = ConvexContains;
        else
            _containsFunc = GenericContains;
    }

    return _containsFunc(other);
}

Each call to Contains after the first will not call IsConvex. Unless I have misunderstood you.. that sounds like what you're after.

Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138
  • The problem with this is that in general polygons are mutable; only in this particular case I know that between calls it won't change. Caching which function to use may result in the wrong method being called after the geometry changes. – Steven Apr 04 '14 at 02:40
  • How many different ways (execution paths) can a `Polygon` be changed? Public properties? Methods? Perhaps the caching should happen in `IsConvex` instead and that should check/set flags relating to the current state of the `Polygon`. – Simon Whitehead Apr 04 '14 at 02:41
  • Essentially a polygon is defined by a list of points; so any change to this list OR to any instance of a point in this list may change the convexness. – Steven Apr 04 '14 at 02:43
  • Also, this list of point is publicly accessible, so essentially the number of possible locations where it changes is infinite. Due to the geometry library being used all over our projects, it is not feasible to change this. – Steven Apr 04 '14 at 02:44
  • 1
    Perhaps you could wrap/inherit from the list and override the methods that modify it (assuming some sort of Add/Remove/Get triad). I am just guessing here though because I can't see the overall picture. – Simon Whitehead Apr 04 '14 at 02:49
  • The idea with my above comment is that you can then be notified of changes .. thereby setting a flag to check next time `IsConvex` is called. – Simon Whitehead Apr 04 '14 at 02:51
  • I like the idea of making a wrapper of some sort to make the list observable, but I'm not sure if it is possible to do this without changing either the class's interface or behavior. The list of points is simply defined as `public List Points {get; set;}`, so any change made to the list by the client after it has been set as the polygon's point list, should be reflected in the polygon (and should also cause a notification). Do you think there is any way to do this? – Steven Apr 04 '14 at 03:34
  • What modifies this list? Just the client code? If so.. you could simply inherit from `List` and wrap the methods (or just change it to an `ObservableCollection` which implements `IList`). If an external library also edits this list.. then that's a different ball game. – Simon Whitehead Apr 04 '14 at 03:41
  • Just client code, but the client code also can create the list. Changing the interface to require out custom list to be passed is not an option, and doing the wrapping internally means that changes to the initial list are either not reflected or not notified of. – Steven Apr 04 '14 at 03:47
  • Let me add a small code example to my question to clarify. – Steven Apr 04 '14 at 03:48
0

Okay...you guys really shot yourselves in the foot by using List in your public interface. Here is how you can try to solve it (there is a small non-zero chance that this will incorrectly cache, but its a 1 in (1 <<32 ) chance.

As you have noted, we could cache the polygon's Convexness...but the problem then becomes a case of Cache invalidation.

Without being able invalid cache on object change. You would have to check for cache coherence on each call. This is where hashing comes in.

By default the hash for list is quite useless, you want to use a this solution for getting a usable hash.

So what you want to do is to add a nullable<int> _cachedHashCode on your polygon. When you call Polygon.IsConvex, you hash your List<Point>, compare with your _cachedHashCode, and if they don't equal, recompute your IsConvex.

Community
  • 1
  • 1
Aron
  • 15,464
  • 3
  • 31
  • 64
  • Nice idea, but as you note, there still is a chance that things go wrong. We can't allow this, as there is no way to automatically detect that things have gone wrong. – Steven Apr 04 '14 at 06:08
  • 1 in 2^32 chances per object being changed, it really isn't very likely. It does depend on how critical your application is of course... – Aron Apr 04 '14 at 07:46