سؤال

I am fetching data from Core Data storage using an NSFetchRequest and store that data in an array - all works great. As a next step, I want to sort that array using NSSortDescriptors like so:

array = [array sortedArrayUsingDescriptors:[NSArray arrayWithObjects:
            [NSSortDescriptor sortDescriptorWithKey:@"score" ascending:NO],
            [NSSortDescriptor sortDescriptorWithKey:@"score" ascending:NO comparator:^NSComparisonResult(id obj1, id obj2) {
                    if ([[[array objectAtIndex:[obj2 integerValue]] valueForKey:@"lessImportantItems"] containsObject:[array objectAtIndex:[obj1 integerValue]]]) {
                        return (NSComparisonResult)NSOrderedAscending;
                    } else {
                        return (NSComparisonResult)NSOrderedDescending;
                    }
                }],
            [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:NO], nil]];

The problem I have is that the NSComparator block in the second NSSortDescriptor isn't called (I tried to NSLog). To give some background to my data structure, here's the relevant Core Data object graph section:

http://screencast.com/t/AOY6IdNFWoK

What the application does is it compares items with one another. As a first step, the winner in a paired comparison gets a score increment. But also I mark a paired priority, i.e. I add a one-to-many lessImportantItems relationship from the winner to the loser. So, in my array I first try to sort by score, and then, when scores are equal, I also try and sort by paired priority.

Maybe it's because I use score as comparator key twice in a row? But, on the other hand, NSComparator does not allow a relationship to be passed as a key either.

I can't seem to crack this one. Anyone has any ideas? Or perhaps I should take a different approach to sorting?

هل كانت مفيدة؟

المحلول

The second sort descriptor does not make any sense to me. It applies the given comparator to the score attribute of the objects to compare. So inside the comparator, obj1, obj2 are the score values of the to-be-compared objects. It seems that you try to get the underlying objects with

[array objectAtIndex:[obj1 integerValue]]
[array objectAtIndex:[obj2 integerValue]]

but that cannot work. So the second sort descriptor should look like

[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:NO
        comparator:^NSComparisonResult(Item *item1, Item *item2) {

        // compare item1, item2 ...
}];

But then the next problem arises: How to compare two objects according to priority? Your code does essentially the following:

if ([item2 valueForKey:@"lessImportantItems"] containsObject:item1]) {
    return NSOrderedAscending;
} else {
    return NSOrderedDescending;
}

But that is not a proper comparator:

  • It does not return NSOrderedSame if the objects are equal (not "reflexive"),
  • For two "unrelated objects" it will return NSOrderedDescending regardless of the order (not "asymmetric"),
  • it does not detect if item1 is only indirectly related to item2 (not "transitive").

But how to sort "unrelated objects"? There is no unique solution. If both B and C are less important than A, then both A, B, C and A, C, B are valid solutions. What should the comparator return when comparing B and C?

So I think that cannot be achieved with a sort descriptor and you have to choose some other algorithm, e.g. "Topological sorting".

نصائح أخرى

If anyone's interested, here's how I achieved the sorting I was after.

I used only the first NSSortDescriptor from the example above to get the array sorted by score, and then I called a further sorting method on that array:

array = [array sortedArrayUsingDescriptors:[NSArray arrayWithObjects:
            [NSSortDescriptor sortDescriptorWithKey:@"score" ascending:NO], nil];

array = [self applyMoreImportantPairOrdering:array];

And here's the method:

+ (NSArray *)applyMoreImportantPairOrdering:(NSArray *)array {

    NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];

    [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

        if ([[obj valueForKey:@"score"] integerValue] > 0) {

            NSMutableSet *lessImportantItemsSet = [NSMutableSet setWithSet:[obj valueForKey:@"lessImportantItems"]];

            for (int i = idx - 1; i >= 0; i--) {

                NSManagedObject *objAbove = [array objectAtIndex:i];

                if ([[obj valueForKey:@"score"] integerValue] == [[objAbove valueForKey:@"score"] integerValue]) {

                    if ([lessImportantItemsSet containsObject:objAbove]) {

                        NSUInteger idxAbove = [mutableArray indexOfObject:objAbove];

                        [mutableArray removeObject:obj];
                        [mutableArray insertObject:obj atIndex:idxAbove];
                    }
                }
            }
        }
    }];

    return [NSArray arrayWithArray:mutableArray];
}

The reason I need the lessImportantItemsSet is because when I move (delete and insert) an item in an array, it loses its lessImportantItems relationships. This I way I maintain the list/set of less important items while I'm done with the particular item.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top