4

I have a Core Data entity called Line. Each line contains an instance of a VerticePoint which contains an x and y property. These x and y vertices form simple 2D polygons.

What I want to do is sort an array of these Line objects which is in random order so that the origin of the shape, the bottom left point, is always the first element in the array, then followed by the remaining vertices wound in counter-clockwise direction from the origin.

So say the points in my original array are (x y axis is centred at 0,0) :

x = 20, y = 20
x = 20 , y= 10
x = 10, y=10
x = 10, y =20
x = 15, y = 10

I want to sort them like so :

x = 10, y=10
x = 15, y = 10
x = 20 , y= 10
x = 20, y = 20
x = 10, y =20

Many thanks

GuybrushThreepwood
  • 5,598
  • 9
  • 55
  • 113

3 Answers3

13

Here's a proposal for a precise specification:

  1. Assume a first quadrant coordinate system (with the y axis pointing up).
  2. Find the center of the axis aligned bounding box of all points.
  3. Sort the points by the angle of a vector from the center to the point. To calculate the angle consider a vector pointing south west to be at 0° with angles ascending in counter-clockwise direction.

And here's a solution:

NSArray *points = @[
    [NSValue valueWithCGPoint:(CGPoint){20, 20}],
    [NSValue valueWithCGPoint:(CGPoint){20, 10}],
    [NSValue valueWithCGPoint:(CGPoint){10, 10}],
    [NSValue valueWithCGPoint:(CGPoint){10, 20}],
    [NSValue valueWithCGPoint:(CGPoint){15, 10}],
];

CGPoint min = [points[0] CGPointValue];
CGPoint max = min;
for (NSValue *value in points) {
    CGPoint point = [value CGPointValue];
    min.x = fminf(point.x, min.x);
    min.y = fminf(point.y, min.y);
    max.x = fmaxf(point.x, max.x);
    max.y = fmaxf(point.y, max.y);
}

CGPoint center = {
    0.5f * (min.x + max.x),
    0.5f * (min.y + max.y),
};

NSLog(@"center: %@", NSStringFromCGPoint(center));

NSNumber *(^angleFromPoint)(id) = ^(NSValue *value){
    CGPoint point = [value CGPointValue];
    CGFloat theta = atan2f(point.y - center.y, point.x - center.x);
    CGFloat angle = fmodf(M_PI - M_PI_4 + theta, 2 * M_PI);
    return @(angle);
};

NSArray *sortedPoints = [points sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    return [angleFromPoint(a) compare:angleFromPoint(b)];
}];

NSLog(@"sorted points: %@", sortedPoints);
Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
1

You can use

- (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors

of NSArray.

You can use more then one descriptor. Just initialize two descriptors one with x, one with y property.

Mert
  • 6,025
  • 3
  • 21
  • 33
0

You should implement method for your VerticePoint object which do the comparison, something like that:

- (NSComparisonResult)compare:(VerticePoint *)vpoint
{
    if (self.x > vpiont.x)
        return NSOrderedAscending;
    else if (self.x < vpiont.x)
        return NSOrderedDescending;
    else if (self.y > vpiont.y)
        return NSOrderedAscending;
    else if (self.y < vpiont.y)
        return NSOrderedDescending;
    else 
        return NSOrderedSame;
}

And after that if you have your array with VerticePoint object you call:

NSArray *sortedArray = [yourArray sortedArrayUsingSelector:@selector(compare:)];

Hope this help.

//EXTENDED

If you don't want to create subclass of NSManagedObject you can use NSSortDescriptor:

NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"ENTITYNAME" inManagedObjectContext:context]];

NSSortDescriptor *sortDescriptorX = [NSSortDescriptor sortDescriptorWithKey:@"yourObjecy.x" ascending:YES];
NSSortDescriptor *sortDescriptorY = [NSSortDescriptor sortDescriptorWithKey:@"yourObjecy.y" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObjects:sortDescriptorX, sortDescriptorY, nil]];

NSArray *sortedResults = [context executeFetchRequest:request error:nil];

// EXTENDED

Or the easiest solution is

NSArray *returnedVertices = [verticesPassed sortedArrayUsingComparator:^(id obj1, id obj2) {
    //Cast to your object:
    VerticePoint *p1 = (VerticePoint*)obj1;
    VerticePoint *p2 = (VerticePoint*)obj2;
    if (p1.x > p2.x)
        return NSOrderedAscending;
    else if (p1.x < p2.x)
        return NSOrderedDescending;
    else if (p1.y > p2.y)
        return NSOrderedAscending;
    else if (p1.y < p2.y)
        return NSOrderedDescending;
    else 
        return NSOrderedSame;
}

];

Greg
  • 25,317
  • 6
  • 53
  • 62
  • What are self.x and self.y ? – GuybrushThreepwood Dec 05 '13 at 09:17
  • I assume that your VerticePoint class contain property float x; and property float y;. You add this method to your class you want to sort, which is VerticePoint, right? And this is condition you want to use when sorting. – Greg Dec 05 '13 at 09:19
  • No - the array contains vertices point core data objects, but the code is not in this class. – GuybrushThreepwood Dec 05 '13 at 09:21
  • You can create subclass of NSManagedObject for vertices point and add this method to it. And by calling sortedArrayUsingSelector: on your array containing vertices point it should work. – Greg Dec 05 '13 at 09:26
  • I am trying this, but am getting an error of unrecognised selector send to NSArrayI : NSArray *sortDescriptorsX = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"origin.x" ascending:YES]]; NSArray *sortDescriptorsY = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"origin.y" ascending:YES]]; NSArray * finishedSort = [NSArray arrayWithObjects:sortDescriptorsX, sortDescriptorsY, nil]; NSArray *returnedVertices = [verticesPassed sortedArrayUsingDescriptors:finishedSort]; return returnedVertices; – GuybrushThreepwood Dec 05 '13 at 09:55
  • On which line do you have this error? Maybe your key: origin.x is wrong. – Greg Dec 05 '13 at 09:58
  • It works if I pass just one sort descriptor, but not both. – GuybrushThreepwood Dec 05 '13 at 09:59
  • @Ohnomycoco I don't know why it's not working for you. I still think that my first solution is the best. I added one more solution in the previous post so fell free to try it. – Greg Dec 05 '13 at 10:21