Recursive sorting of an NSArray using an NSComparisonResult

146 views Asked by At

I'm trying to sort an array that can contain different types of structures within itself. Right now I can't seem to get it to recursively sort itself when it has subarrays in the root array.

In the following code below, I have a test array that contains two sub arrays, but the returned root array is moving the items around in the already sorted subarrays for some reason. The subarrays get properly sorted using recursion, but after the recursion the subarrays lose their previous sort. I'd appreciate any help offered.

Output of sorted subarray:

(
    A,
    b,
    C,
    c,
    Cc,
    cc,
    CC,
    hhh,
    x,
    z
)

This is the output of the sorted root array (not what I want):

 (
        (
        z,
        A,
        b,
        hhh,
        x,
        Cc,
        C,
        cc,
        CC,
        c
    ),
        (
        z,
        A,
        b,
        hhh,
        x,
        Cc,
        C,
        cc,
        CC,
        c
    )
)

Code:

- (void)test
{
    NSArray *testArr = @[ @[@"z",@"A",@"b",@"hhh",@"x",@"Cc",@"C",@"cc",@"CC",@"c"],
                          @[@"z",@"A",@"b",@"hhh",@"x",@"Cc",@"C",@"cc",@"CC",@"c"]
                        ];

    self.t = [NSMutableArray arrayWithArray:[self sortedArray:testArr]];

    NSLog(@"%@",self.t);
}

- (NSArray *)sortedArray:(NSArray *)input
{
    NSArray *newArray = [input sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2)
    {
        NSString *nameOne = @"";
        NSString *nameTwo = @"";

        if ([obj1 isKindOfClass:[NSString class]])
        {
            nameOne = obj1;
            nameTwo = obj2;
        }
        else if ([obj1 isKindOfClass:[NSArray class]])
        {
            NSArray *sorted1stArray = [self sortedArray:obj1];
            NSArray *sorted2ndArray = [self sortedArray:obj2];

            NSLog(@"%@",sorted1stArray);
            NSLog(@"%@",sorted2ndArray);

            if ([sorted1stArray.firstObject isKindOfClass:[NSDictionary class]])
            {
                NSDictionary *firstDict = sorted1stArray.firstObject;
                NSDictionary *secondDict = sorted2ndArray.firstObject;

                if (firstDict[@"Title"])
                {
                    nameOne = firstDict[@"Title"];
                    nameTwo = secondDict[@"Title"];
                }
            }
            else if ([sorted1stArray.firstObject isKindOfClass:[NSString class]])
            {
                nameOne = sorted1stArray.firstObject;
                nameTwo = sorted2ndArray.firstObject;
            }
        }
        else if ([obj1 isKindOfClass:[NSDictionary class]])
        {
            if (obj1[@"Title"])
            {
                nameOne = obj1[@"Title"];
                nameTwo = obj2[@"Title"];
            }
        }

        return [nameOne localizedCaseInsensitiveCompare:nameTwo];
    }];
    return newArray;
}
2

There are 2 answers

0
Sulthan On BEST ANSWER

The comparator is called for every comparison. That means it is invoked multiple times for every element. Every subarray is then sorted multiple times.

Before first sorting, you should backtrack the structure and sort the inner structures first instead of having a recursive comparator. In other words, don't mix comparator with sorting. Keep it separate.

The code could have the following structure:

- (NSArray *)sortedArray:(NSArray *)input {
   //1. sort every subelement in input recursively

   //2. sort input
}

Also note that the sorting of substructures is not reflected in the result. It's not saved anywhere, it's just temporarily created in the comparator.

0
vikingosegundo On

if it is just strings in arrays in an array, why the hassle of a recursive solution?

NSArray *testArr = @[[@[@"z",@"A",@"b",@"hhh",@"x",@"Cc",@"C",@"cc",@"CC",@"c"] mutableCopy],
                     [@[@"z",@"A",@"b",@"hhh",@"x",@"Cc",@"C",@"cc",@"CC",@"c"] mutableCopy
                    ];        
[testArr enumerateObjectsUsingBlock:^(NSMutableArray *subarray, NSUInteger idx, BOOL *stop) {
   [subarray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj1 localizedCaseInsensitiveCompare:obj2];
   }];
}];

… needs to be sorted using a certain sequence (not just A-Z)

A look up would be a reasonable solution as I show here: Sort NSmutablearray with two special keys, or your own compare method that needs to return a NSComparisonResult

enum {
   NSOrderedAscending = -1,
   NSOrderedSame,
   NSOrderedDescending 
};
typedef NSInteger NSComparisonResult;

You can define that method in a category

@implementation NSString (MySorting)
-(NSComparisonResult)localizedCaseInsensitiveCompareLongestFirst:(NSString *)string
{
    if ([self length] > [string length]) {
        return NSOrderedAscending;
    } else if ([self length] < [string length]) {
        return NSOrderedDescending;
    } else {
        return [self localizedCaseInsensitiveCompare:string];
    }
}
@end

Now alter the first code

NSArray *testArr = @[ [@[@"z",@"A",@"b",@"hhh",@"x",@"Cc",@"C",@"cc",@"CC",@"c"] mutableCopy],
                      [@[@"z",@"A",@"b",@"hhh",@"x",@"Cc",@"C",@"cc",@"CC",@"c"] mutableCopy]
                      ];


[testArr enumerateObjectsUsingBlock:^(NSMutableArray *subarray, NSUInteger idx, BOOL *stop) {
   [subarray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
       return [obj1 localizedCaseInsensitiveCompareLongestFirst:obj2];
   }];
}];

and we have the custom sorting of

(
        (
        hhh,
        Cc,
        cc,
        CC,
        A,
        b,
        C,
        c,
        x,
        z
    ),
        (
        hhh,
        Cc,
        cc,
        CC,
        A,
        b,
        C,
        c,
        x,
        z
    )
)