0

I have a list of Points (x,y) that I want to sort by their polar angle with respect to a reference point which I calculate as the average point of all points in the list. How can I set up IComparer so that I can pass the reference point to it for calculating the polar angle?

2 Answers2

3

You can calculate the reference point ahead of time and pass it to the constructor of your IComparer:

class PointComparer : IComparer<Point>
{
    private readonly Point referencePoint;

    public PointComparer(Point referencePoint)
    {
        this.referencePoint = referencePoint;
    }
    public Int32 Compare(Point x, Point y)
    {
        // Compare using referencePoint
    }
}

Usage:

var ordered = myList.OrderBy(x => x, new PointComparer(calculatedReferencePoint));

I used OrderyBy and not Sort because the latter is considered not a stable sort when we refer to the documentation.

Zein Makki
  • 29,485
  • 6
  • 52
  • 63
  • You can also derive from the _class_ type `Comparer`. You will still need a `Compare(Point, Point)` method (this time an `override` of the inherited `abstract` method from the base class), but the advantage is you get support for both `IComparer` and the old non-generic `IComparer`. – Jeppe Stig Nielsen Jan 29 '17 at 12:36
  • When you use `OrderBy`, you can also skip the custom class and just capture (close over) that reference point in the `Comparison` delegate (lambda). That would be `myList.OrderBy(x => /* expression involving x and calculatedReferencePoint */)`. If you really need a comparer, you can get it with `Create`, as in `Comparer.Create(x => /* expression involving x and calculatedReferencePoint */)`, again eliminating the need for writing the custom class yourself. – Jeppe Stig Nielsen Jan 29 '17 at 12:41
1

You have two options.

Option A

The reference point must be part of your Point object. So that in your Point's Compare method, you could take it into consideration to calculate the angle and compare the objects.

public int IComparer.Compare(Point x, Point y)  {
  var angleX = Utilities.CalculateAngle(x.ReferencePoint);
  var angleY = Utilities.CalculateAngle(y.ReferencePoint);

  if (angleX < angleY) return -1;
  if (angleX == angleY) return 0;
  if (angleX > angleY) return 1;

  // Or simply "return angleX - angleY;"
}

Option B

The Angle should be already calculated in your point. Make the Point an immutable structure and calculate the angle at the time of instantiate of the structure. Then just OrderBy that property.

i.e.

public struct Point
{
  public double Angle { get; private set; }

  public Point(double referencePoint, double x, double y)
  {
    // TODO: Calculate Angle
  }
}

points.OrderBy(p => p.Angle);
hyankov
  • 4,049
  • 1
  • 29
  • 46
  • Your **Option A** `IComparer.Compare` cause compilation error, because not every path return value. – user4003407 Jan 29 '17 at 10:59
  • k, then just `return angleX - angleY;`, as I commented in the code – hyankov Jan 29 '17 at 11:02
  • Is not angle supposed to be some floating point type? AFAIK, no of them have implicit conversion to `int`. So, you still have error here. – user4003407 Jan 29 '17 at 11:07
  • I didn't really put much thought into that, it's more of a pseudo-code – hyankov Jan 29 '17 at 11:45
  • @PetSerAl The best option is `return angleX.CompareTo(angleY);`. It works for both `int`, `float`, and `double` (and more). And even in the `int` case it is better, if there is a risk the subtraction Hristo Yankov suggests, can overflow (a difference exceeding `int.MaxValue` can become negative, and similarly for `MinValue`). Also semantically or "logically" it expresses what we want. – Jeppe Stig Nielsen Jan 29 '17 at 12:52