How to sort this kind of case?
I have a NSMutableArray.
My NSMutableArray can have an object or NSMutableDictionary.
My NSMutableDictionary only has 1 object (an object as its object and a string as its key)
Both object is the same and has attribute name
Now, what I'm struggle with is I want to sort the NSMutableArray based on the dictionary key and the object's name? (let's just ignore the object that is added to NSMutableArray)
Bunch of thanks to person can give me idea how to solve this case!
Add your NSMutableArray to a custom subclass of NSObject and sort using a custom NSComparator, like so:
(NSComparator)^(id obj1, id obj2){ /* obj1 and obj2 are NSMutableArrays */
if ([obj1 objectAtIndex:1] isKindOfClass:[NSMutableDictionary class]) {
// sort according to the dictionary case
} else { // [obj1 objectAtIndex:1] isKindOfClass:[NSObject class]
// sort according to the your other, default case
}
}
Do note that this is untested and that you'll need to fill in the bodies of the if and else statements. The comment by the else clause is just how I code and you can eliminate it if it doesn't conform to your taste.
Related
Without unintentionally killing performance, does this appear at first glance to be acceptable for perhaps 200 guid strings in one list compared for equality with 100 guid strings from another list to find the matching indexes.
I have a method signature defined like so...
-(NSArray*)getItemsWithGuids:(NSArray*)guids
And I wanted to take that passed in array of guids and use it in conjunction with this array...
NSArray *allPossibleItems; // Has objects with a property named guid.
... to obtain the indexes of the items in allPossibleItems which have the matching guids from guids
My first instinct was to try indexesOfObjectsPassingTest but after putting together the block, I wondered whether the iOS framework already offers something for doing this type of compare more efficiently.
-(NSArray*)getItemsWithGuids:(NSArray*)guids
{
NSIndexSet *guidIndexes = [allPossibleItems indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
SomeObjWithGuidProperty *someObject = obj;
for (NSString *guid in guids) {
if ([someObject.guid isEqualToString:guid]) {
return YES;
}
}
return NO;
}];
if (guidIndexes) {
// Have more fun here.
}
}
Since you're working with Objective-C (not Swift) check out YoloKit. In your case, you can do something like:
guids.find(^(NSString *guid){
return [someObject.guid isEqualToString:guid];
});
My thought would be to use a set -
-(NSArray*)getItemsWithGuids:(NSArray*)guids inAllObjects:(NSArray *)allObjects
{
NSSet *matchGuids=[NSSet setWithArray:guids];
NSMutableArray *matchingObjects=[NSMutableArray new];
for (SOmeObjectWithGuidProperty *someObject in allObjects) {
if ([matchGuids contains:someObject.guid]) {
[matchingObjects addObject:someObject];
}
}
return [matchingObjects copy];
}
Your code looks like it would have O(n^2) performance, which is bad. I think the solution of converting guids to an NSSet and then using NSSet's containsObject would likely be much more performant. You could rewrite your indexesOfObjectsPassingTest code to use an NSSet and containsObject pretty easily.
If order doesn't matter much, I would suggest to change data structure here. Instead of using NSArray, consider to use NSDictionary with guid as key and someObject as value. In this case, you should use -[NSDictionary objectsForKeys:notFoundMarker:] method to obtain objects.
It will work much faster, than enumeration trough 2 arrays. If the NSDictionary key have a good hash function, accessing an element, setting an element, and removing an element all take constant time. NSString has good hash.
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSArray *objectsAndNulls = [allPossibleItemsDictionary objectsForKeys:guids notFoundMarker:[NSNull null]];
if (objectsAndNulls) {
// Have more fun here.
// You should check that object in objectsAndNulls is not NSNull before using it
}
return objectsAndNulls;
}
UPD Unfortunately, there is no way to pass nil as notFoundMarker. If you can't provide usable notFoundMarker value and don't want to perform additional checks, you can query objects one by one and fill NSMutableArray. In this case you will avoid pass trough array to remove NSNulls:
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:guids.count];
for (NSString *guid in guids) {
SomeObjWithGuidProperty *object = allPossibleItemsDictionary[guid];
if (nil != object) {
[objects addObject:object];
}
}
if (nil != objects) {
// Have more fun here.
}
return object;
}
I have 3 objects that might be or not initialized in a random order.
so, if objects "objectOne, "objectTwo", "objectThree" are initialized in this order with
myArray = [NSArray arrayWithObjects:objectOne,objectTwo,objectThree nil];
all objects get inside the array without problem but in my case objectOne, objectTwo might be nil and objectThree might not be nil, and in this case I would like myArray to return(count) 1.
if objectOne is nil but objectTwo and objectThree are not nil I want my array to return(count) 2.
In these 2 last cases my array always return nil. What would be the best approach to this?
There are no magic method can solve the problem for you, you need to build the array from NSMutableArray
NSMutableArray *array = [NSMutableArray array];
if (objectOne) [array addObject:objectOne];
if (objectTwo) [array addObject:objectTwo];
if (objectThree) [array addObject:objectThree];
arrays can't contain nil. There is a special object, NSNull ([NSNull null]), that serves as a placeholder for nil. You can put NSNull in an array, but I don't think that solves your problem either.
How about this:
Create an empty mutable array.
In 3 separate statements:
If objectOne is not nil, add it to the array
if objectTwo is not nil, add it to the array
If objectThree is not nil, add it to the array.
If you need your objects to be in random order, scramble the array afterwords:
for (int index = 0; index < array.count; index++)
{
int randomIndex = arc4random_uniform()
[array exchangeObjectAtIndex: index withObjectAtIndex: randomIndex];
}
This is known as a Fisher–Yates shuffle. (or a minor variation on Fisher-Yates, anyway.)
If you're doing this rarely and you aren't trying to make things neat, you can, of course, use a mutable array and either add or don't add the items one at a time in code, depending on whether they are nil.
If you're doing this frequently and you want a syntax that looks similar to the array literal notation, you can take advantage of the C preprocessor and C arrays to create a smarter NSArray class constructor that handles nil:
#define NSArrayWithCArray(array) \
[NSArray arrayWithCArray:cArray count:sizeof(cArray) / sizeof(cArray[0])];
id cArray[] = {
object1,
object2,
object3,
...
};
NSArray *array = NSArrayWithCArray(cArray);
and then define a method on NSObject that walks through the C array programmatically, dropping any nil values.
+ (NSArray *)arrayWithCArray:(__strong id[])cArray count:(NSUInteger)count {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
for (__strong id *item = cArray; item < cArray + count; item++) {
if (*item != nil) {
[array addObject:*item];
}
}
return array;
}
Note: The code above is untested, but at least close enough to give you an idea of how to do it. :-)
I have an NSArray of custom objects and would like to filter down that array to be unique on a specific key. Most of the things I've seen while searching for an answer involve using valueForKey:, valueForKeyPath: or #distinctUnionOfObjects but those return arrays of values for that key. I want the whole object instead.
The objects are subclassed PFObjects from Parse so they are KVC compliant, and I would like them to be filtered on the objectId key.
Put this in a category on NSArray:
-(NSArray*)arrayFilteredForUniqueValuesOfKeyPath:(NSString*)keyPath
{
NSMutableSet* valueSeen = [NSMutableSet new];
return [self filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
id value = [evaluatedObject valueForKeyPath:keyPath];
if(![valueSeen containsObject:value])
{
[valueSeen addObject:value];
return true;
}
else
{
return false;
}
}]];
}
Of course, the concept is kind of flawed since you really have no way of determining which of the n objects that have any give value for the keyPath you really wanted (in this case you get the first one)
This question already has answers here:
How do I sort an NSMutableArray with custom objects in it?
(27 answers)
Closed 9 years ago.
Hopefully someone can help.
I'm adding multiple objects to a NSMutableArray and I need to sort the order based on the first element which will always be a number.
However I'm unsure how to do this?
For example:
NSMutableArray *array = [[NSMutableArray alloc] init];
NSArray *object = [NSArray arrayWithObjects: #"1",#"Test",#"Test"];
[array addObject:object];
Thanks
If your array always contains other arrays, and the first element of the innermost array is always a string containing a number, you could use the NSMutableArray method sortUsingComparator to sort your array:
[array sortUsingComparator: ^(NSArray* obj1, NSArray* obj2)
{
int value1 = [obj1[0] integerValue];
int value2 = [obj2[0] integerValue];
if (value1==value2)
return NSOrderedSame;
else if (value1 < value2)
return NSOrderedAscending;
else
return NSOrderedDescending;
}
];
In the sortUsingComparator family of methods, you supply a block of code that the sort method uses to compare pairs of objects in your array. The block uses the standard typedef NSComparator, which takes 2 objects as parameters and returns a value of type NSComparisonResult.
The code above will probably crash if all the objects in your array are not arrays of strings. (Actually it would work if the first element of each component array was an NSNumber, since NSNumber also responds to the integerValue message.)
If you are going to use this code in a very controlled environment where you can be sure that the data you are sorting is well-formed, it should work as written. If there is any chance that the objects in the array would be of a different type, or be empty, or that their first element would not respond to the integerValue messages, then you should add error checking code.
If you sort your array alphanumerically, the object #"1" will appear before any words. Keep in mind though that #"1" in your code above is a string, not a number.
As to how to sort an array, look into [NSArray sortedArrayUsingComparator:] and similar methods.
I have a custom class extending NSObject. I am maintaining NSMutableArray of this class objects. Here is the situation,
customObject-class {
NSString *name;
int ID;
.....and many other properties;
}
customObjectsArray [
customObject1,
customObject2,
...etc
]
Now I am trying to use filterUsingPredicate to remove objects that has nil names, like below but it returns very few or none objects while I know that there are hundreds of objects that has name not nil or empty. Could someone please tell me what could be wrong here.
[customObjectsArray filterUsingPredicate:[NSPredicate predicateWithFormat:#"name != nil"]];
Why won't you try like this:
NSMutableArray *array=...;
[array filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
CustomObject *customObject=(CustomObject *) evaluatedObject;
return (customObject.name!=nil);
}]];
As I replied to #rdelmar, I found an issue. This predicate was getting called before customObject1's data were actually initialised. I should check the status of data flag that says data has been initialised for this particular object and then apply filter. It worked. If data is not initialised all object's name is off course nil!