I have an NSArray of Object that has an interesting property that I would like to use in the following way: Given my array of objects with properties:
Object1 - Property A;
Object2 - Property A;
Object3 - Property B;
Object4 - Property D;
Object5 - Property D;
Object6 - Property D
I want these to be bucket sorted by their properties into a new array:
Array1 - Objects Object1, Object2
Array2 - Objects Object3
Array3 - Objects Object 4, Object5, Object6
And then within each array, sort by using a timeStamp property.
I have tried to accomplish this naively by creating a dictionary, adding interesting objects to the dictionary by property like if ([dictionary objectForKey:#"propertyVal"]) //add object else // create array for key, add object to array. This approach has not worked as expected because I end up needing to dekey the NSMutableDictionary using allKeysForValue, which is not reliable.
I feel that this is a fairly common problem and I would love to hear any insight into how I might go about solving this. Code is great, but even an algorithm (with the appropriate objects to use) should suffice.
It's not a proper bucket sort, but should work for a set of three properties. A bit of fiddling and you should be able to adjust it for any number of properties:
Edit. I made a dynamic version (just set property type to what you need):
- (NSMutableArray *)order:(NSDictionary *)objects byProperty:(id)property {
NSMutableSet *propertySet = [NSMutableSet setWithCapacity:5]; // so we can count the unique properties
for (Object *obj in [objects allValues]) {
[propertySet addObject:[obj property]];
}
NSMutableArray *objectCollections = [NSMutableArray arrayWithCapacity:[propertySet count]];
// create arrays for every property
for (int i = 0; i < [objects allValues]; i++) {
NSMutableArray *collection = [NSMutableArray arrayWithCapacity:5];
[objectCollections addObject:collection];
}
NSArray *allProperties = [propertySet allObjects];
// push objects into arrays according to a certain property
for (Object *obj in [dictionary allValues]) {
[[objectCollections objectAtIndex:[allProperties indexOfObject:[obj property]] addObject:obj];
}
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[objectCollections count]];
// sort arrays by timestamp
for (int i = 0; i < [objectCollections count]; i++) {
[result addObject:[[objectCollections objectAtIndex:i] sortedArrayUsingComparator:^(id obj1, id obj2) {
if ([(Object *)obj1 timeStamp] > [(Object *)obj2 timeStamp]) {
return (NSComparisonResult)NSOrderedAscending;
}
if ([(Object *)obj1 timeStamp] < [(Object *)obj2 timeStamp]) {
return (NSComparisonResult)NSOrderedDescending;
}
return (NSComparisonResult)NSOrderedSame;
}];
}
return result;
}
Related
This question already has answers here:
How do I sort an NSMutableArray with custom objects in it?
(27 answers)
Sorting two NSArrays together side by side
(4 answers)
Closed 9 years ago.
I have 2 NSMutableArrays. First array contains custom object intances with property NSString *itemID, second array contains only from NSString objects with same values of itemID, but in another order. I need to sort first array by itemID property of each object, and it should be sorted like second array.
How I can do this?
guideArray = < YOUR SECOND ARRAY WITH STRING OBJECT >;
unsortedArray = < YOUR FIRST ARRAY WITH CUSTOM OBJECT >;
[unsortedArray sortUsingComparator:^(id o1, id o2) {
Items *item1 = o1;
Items *item2 = o2;
NSInteger idx1 = [guideArray indexOfObject:item1.ItemID];
NSInteger idx2 = [guideArray indexOfObject:item2.ItemID];
return idx1 - idx2;
}];
NSLog(#"%#",unsortedArray);
Store the custom objects in an dictionary with itemID as key, use this dictionary as lookup to sort the objects:
NSArray *objects; // your objects
NSMutableArray *hintArray; // your sorted IDs
NSMutableDictionary *lookupDict = [[NSMutableDictionary alloc] initWithCapacity:[objects count]];
NSMutableArray *sortedObjects = [[NSMutableArray alloc] initWithCapacity:[hintArray count]];
for (id object in objects) {
[lookupDict setValue:object forKey:[object itemID]];
}
for (id hint in hintArray) {
[sortedObjects addObject:[lookupDict valueForKey:hint]];
}
EDIT:
Solution with inplace sort of objects:
NSMutableArray *objects;
NSMutableArray *hintArray;
NSMutableDictionary *lookupDict = [[NSMutableDictionary alloc] initWithCapacity:[hintArray count]];
int i = 0;
for (NSString *itemID in hintArray) {
[lookupDict setValue:[NSNumber numberWithInt:i] forKey:itemID];
i++;
}
[objects sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [[lookupDict valueForKey:[obj1 itemID]] compare:[lookupDict valueForKey:[obj2 itemID]]];
}];
You can compare your two objects using following syntax :-
[items sortUsingComparator:^NSComparisonResult(Attribute *obj1, Attribute *obj2)
{
return [[NSNumber numberWithInt:[stringOrder indexOfObject:obj1.itemID]] compare:[NSNumber numberWithInt:[stringOrder indexOfObject:obj2.itemID]]]
}];
or else you can use following snippet :
NSArray* sortedKeys = [dict keysSortedByValueUsingComparator:^(id obj1, id obj2)
{
return [obj1 compareTo:obj2];
}
Enjoy Programming !
I am attempting to use this array cleaning method, and there seems to be an error. I can't spot it, I know the array goes in with 3116 items, comes out with 3116 (and I know for a fact there are three duplicates.
Please advice, thanks!
-(NSArray*) removeDuplicates:(NSArray*)inputArray{
NSMutableArray *arrayToClean = [NSMutableArray arrayWithArray:inputArray];
for (int i =0; i<[arrayToClean count]; i++) {
for (int j=(i+1); j < [arrayToClean count]; j++) {
if ([[arrayToClean objectAtIndex:i] isEqual:[arrayToClean
objectAtIndex:j]]) {
[arrayToClean removeObjectAtIndex:j];
j--;
}
}
}
NSArray *arrayToReturn = [NSArray arrayWithArray:arrayToClean];
return arrayToReturn;
}
NSSet will make this a lot easier:
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSSet *unique = [NSSet setWithArray:inputArray];
return [unique allObjects];
}
Please note that a set has no guaranteed order. If you need the objects in the array to be in a specific order then you should sort the resulting array as needed.
It may also be appropriate to use an NSSet instead of the original array, then you don't need to worry about duplicates at all. But this depends on the other needs of your array.
Hey You can use another alternative for this.You can use the NSSet here for this task.
NSSet declares the programmatic interface for static sets of distinct objects
You can use sets as an alternative to arrays when the order of elements isn’t important and performance in testing whether an object is contained in the set is a consideration—while arrays are ordered, testing for membership is slower than with sets.
You Just need To call below method.
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSSet *finalData = [NSSet setWithArray:inputArray];
return [finalData allObjects];
}
If really face any problem in above way of cleaning ducplicates then you can try another Alterantive.
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSMutableArray *inputArray1=[NSMutableArray arrayWithArray:inputArray];
NSMutableArray *finalARray=[[NSMutableArray alloc]init];
for (id obj in inputArray1)
{
if (![finalARray containsObject:obj])
{
[finalARray addObject: obj];
}
NSLog(#"new array is %#",finalARray);
}
return finalARray;
}
I hope it may help you ...
Here is a helper function I had in a previous project to do the exact same thing
- (NSMutableArray *)removeDuplicates:(NSMutableArray *)sortedArray{
NSMutableSet* valuesAdded = [NSMutableSet set];
NSMutableArray* filteredArray = [[NSMutableArray alloc] init];
NSString* object;
/* Iterate over the array checking if the value is a member of the set. If its not add it
* to the set and to the returning array. If the value is already a member, skip over it.
*/
for (object in sortedArray){
if (![valuesAdded member:object]){
[valuesAdded addObject:object];
[filteredArray addObject:object];
}
}
return filteredArray;
}
I'm stuck at following problem for quite some time now:
I've got two NSArrays, both containing NSManagedObject subclass-objects.
They're fed by different sources but the objects in them still have the same properties/values.
What I want to do now is check if array A contains objects from array B and vice versa.
Unfortunately NSArray's containsObject-method doesn't seem to work here.
I think it uses id-testing for the equality check on each object, doesn't it?
So, does anybody have a clue, what to try?
I even tried to encapsulate my objects in NSSets, using member: as my comparison-method but this didn't work out as well, especially because "you must not override" isEqual etc. for NSManagedObject subclasses.
Here's a code snippet:
//manufacturers is an array, parsed out of some xml here...
for(Manufacturer *manu in [fetchedResultsController fetchedObjects])
{
if(![manufacturers containsObject:manu])
{
NSLog(#"Deleting %#", manu.name);
[self.mContext deleteObject:manu];
}
}
for(Manufacturer *manu in manufacturers)
{
if(![[fetchedResultsController fetchedObjects] containsObject:manu])
{
NSLog(#"Adding %#", manu.name);
[newArray addObject:manu];
}
}
Thanks in advance for any hint ;)
I'm not sure if this works, but you could try to match the dictionaries you get with dictionaryWithValuesForKeys:.
Something like this:
NSArray *keysToCompare = [NSArray arrayWithObjects:#"FooAttribute", #"BarAttribute", nil];
// create an array with the dictionary representation of the managedObject
NSMutableArray *fetchedObjectsDictionaries = [NSMutableArray arrayWithCapacity:[[fetchedResultsController fetchedObjects] count]];
for (NSManagedObject *object in [fetchedResultsController fetchedObjects]) {
NSDictionary *dictionaryRepresentation = [object dictionaryWithValuesForKeys:keysToCompare];
[fetchedObjectsDictionaries addObject:dictionaryRepresentation];
}
// another array with dictionaries for managedObjects
NSMutableArray *manufacturersDictionaries = [NSMutableArray arrayWithCapacity:[manufacturers count]];
for (NSManagedObject *object in manufacturers) {
NSDictionary *dictionaryRepresentation = [object dictionaryWithValuesForKeys:keysToCompare];
[manufacturersDictionaries addObject:dictionaryRepresentation];
}
// compare those dictionaries
for (NSInteger i = 0; i < [fetchedObjectsDictionaries count]; i++) {
NSDictionary *dictionary = [fetchedObjectsDictionaries objectAtIndex:i];
if (![manufacturersDictionaries containsObject:dictionary]) {
// get the corresponding managedObject
NSManagedObject *object = [[fetchedResultsController fetchedObjects] objectAtIndex:i];
[newArray addObject:object];
}
}
if that won't work you can write your own isEqualToManufacturer: method and enumerate trough the arrays manually.
There would be 3 types of equality you can check for: same memory address, managed object id equality, and value equality. Your current code already checks to see if the objects share the same memory address and this is most likely not what you are interested in. This leaves two possible options. Using the managed object id equality method you can check if the manufacturers point to the same row in the database. Using the value equality you can check if two manufacturers are equal based on the shared values. Below is a way to check for NSManagedObjectID equality.
for(Manufacturer *manu in [fetchedResultsController fetchedObjects])
{
id databaseIDTest = ^(Manufacturer * checkManu, NSUInteger idx, BOOL *stop){
return [[checkManu objectID] isEqual:[manu objectID]];
};
if([manufacturers indexOfObjectPassingTest:databaseIDTest] == NSIndexNotFound)
{
NSLog(#"Deleting %#", manu.name);
[self.mContext deleteObject:manu];
}
}
for(Manufacturer *manu in manufacturers)
{
id databaseIDTest = ^(Manufacturer * checkManu, NSUInteger idx, BOOL *stop){
return [[checkManu objectID] isEqual:[manu objectID]];
};
NSArray * fetchedObjects = [fetchedResultsController fetchedObjects];
if([fetchedObjects indexOfObjectPassingTest:databaseIDTest] == NSIndexNotFound)
{
NSLog(#"Adding %#", manu.name);
[newArray addObject:manu];
}
}
You need to override -isEqual: since that's what -[NSArray containsObject:] calls into:
- (BOOL)isEqual:(id)other;
{
if (![other isKindOfClass:[Manufacturer class]]) {
return NO;
}
Manufacturer *otherManufacturer = other;
return ([self.name isEqual:otherManufacturer.name] &&
...
);
}
Checking for containment inside an NSSet is cheaper (and may make sense if you run into performance problems). It only works if you have a relatively decent -hash implementation, but it's easy to implement like this:
- (NSUInteger)hash;
{
return [self.name hash] + [self.foo hash] + ...;
}
Don't go trough too much trouble with the hash, just use 2 - 3 values that are most likely to uniquely identify the object.
For the purpose of asking this question about ordering. The following MyObject class returns an instance with random generated category names.
I use the following dataSource methods:
numberOfSections accessed with [dataSource count].
titleForSection accessed with [[dataSource objectAtIndex:indexPath.section] valueForKey:#"categoryName"].
numberOfRowsInSection accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] count].
And finally, the MyObject for each row is accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] objectAtIndex:indexPath.row] on the cellForRowAtIndexPath method.
I use the following code to create a dataSource that displays 9 section categories, however I'm a little stuck on the ordering of these categories and the data within. Assume there's an NSDate property as part of the MyObject class.
Question: How would I go about using this to display the records in descending order?
- (void)createDatasource
{
NSInteger numberOfObjects = 10;
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:numberOfObjects];
NSMutableArray *categories = [NSMutableArray arrayWithCapacity:numberOfObjects];
for (int i = 0; i < numberOfObjects; i++)
{
MyObject *obj = [[MyObject alloc] init];
[objects addObject:obj];
[categories addObject:obj.category];
[obj release];
}
NSSet *set = [NSSet setWithArray:categories];
NSMutableArray *dataSource = [[NSMutableArray alloc] initWithCapacity:[set count]];
for (NSString *categoryString in set)
{
NSMutableDictionary *mainItem = [[NSMutableDictionary alloc] initWithObjectsAndKeys:nil, #"categoryName", nil, #"myObjects", nil];
NSMutableArray *mainItemMyObjects = [NSMutableArray array];
[mainItem setValue:categoryString forKey:#"categoryName"];
for (MyObject *obj in objects)
{
if ([obj.category isEqualToString:categoryString])
{
[mainItemMyObjects addObject:obj];
}
}
[mainItem setValue:mainItemMyObjects forKey:#"myObjects"];
[dataSource addObject:mainItem];
[mainItem release];
}
NSLog (#"objects = %#\ncategories = %#\nset = %#\ndatasource = %#", objects, categories, set, dataSource);
}
Easiest would be to sort your arrays, using NSMutableArray's sorting mutators or NSArray's sorting methods. Otherwise you'd have to construct some sort of mapping from input indices to dataSource indices for use by the various data source methods.
Edit Requested sample code for sorting, something like this should work. I assume you are wanting to sort everything by a property named date on the MyObject.
// First, sort the myObject mutable array in each category
for (NSDictionary *d in dataSource) {
[[d valueForKey:#"myObjects"] sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Compare dates. NSDate's 'compare:' would do ascending order, so if we just
// reverse the order of comparison they'll come out descending.
return [[o2 date] compare:[o1 date]];
}];
}
// Second, sort the categories by the earliest dated object they contain
[dataSource sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Extract the first object from each category's array, which must be the
// earliest it contains due to the previous sort.
MyObject *myObject1 = [[o1 valueForKey:#"myObjects"] objectAtIndex:0];
MyObject *myObject2 = [[o2 valueForKey:#"myObjects"] objectAtIndex:0];
// Compare dates, as above.
return [[myObject2 date] compare:[myObject1 date]];
}];
I need to sort a NSDictionary of dictionaries. It looks like:
{//dictionary
RU = "110.1"; //key and value
SG = "150.2"; //key and value
US = "50.3"; //key and value
}
Result need to be like:
{//dictionary
SG = "150.2"; //key and value
RU = "110.1"; //key and value
US = "50.3"; //key and value
}
I am trying this:
#implementation NSMutableDictionary (sorting)
-(NSMutableDictionary*)sortDictionary
{
NSArray *allKeys = [self allKeys];
NSMutableArray *allValues = [NSMutableArray array];
NSMutableArray *sortValues= [NSMutableArray array];
NSMutableArray *sortKeys= [NSMutableArray array];
for(int i=0;i<[[self allValues] count];i++)
{
[allValues addObject:[NSNumber numberWithFloat:[[[self allValues] objectAtIndex:i] floatValue]]];
}
[sortValues addObjectsFromArray:allValues];
[sortKeys addObjectsFromArray:[self allKeys]];
[sortValues sortUsingDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"floatValue" ascending:NO] autorelease]]];
for(int i=0;i<[sortValues count];i++)
{
[sortKeys replaceObjectAtIndex:i withObject:[allKeys objectAtIndex:[allValues indexOfObject:[sortValues objectAtIndex:i]]]];
[allValues replaceObjectAtIndex:[allValues indexOfObject:[sortValues objectAtIndex:i]] withObject:[NSNull null]];
}
NSLog(#"%#", sortKeys);
NSLog(#"%#", sortValues);
NSLog(#"%#", [NSMutableDictionary dictionaryWithObjects:sortValues forKeys:sortKeys]);
return [NSMutableDictionary dictionaryWithObjects:sortValues forKeys:sortKeys];
}
#end
This is the result of NSLog:
1)
{
SG,
RU,
US
}
2)
{
150.2,
110.1,
50.3
}
3)
{
RU = "110.1";
SG = "150.2";
US = "50.3";
}
Why is this happening? Can you help me with this problem?
NSDictionary are unsorted by nature. The order of the objects as retrieved by allKeys and allValues will always be undetermined. Even if you reverse engineer the order it may still change in the next system update.
There is however more powerful alternatives to allKeys that are used to retrieve the keys in a defined and predictable order:
keysSortedByValueUsingSelector: - Useful for sorting in ascending order according to the compare: method of the value objects.
keysSortedByValueUsingComparator: - New in iOS 4, use a block to do the sort inline.
WOW. Thanx, PeyloW! It's what i needed! I also find this code and it helps me to reorder results:
#implementation NSString (numericComparison)
- (NSComparisonResult) floatCompare:(NSString *) other
{
float myValue = [self floatValue];
float otherValue = [other floatValue];
if (myValue == otherValue) return NSOrderedSame;
return (myValue < otherValue ? NSOrderedAscending : NSOrderedDescending);
}
- (NSComparisonResult) intCompare:(NSString *) other
{
int myValue = [self intValue];
int otherValue = [other intValue];
if (myValue == otherValue) return NSOrderedSame;
return (myValue < otherValue ? NSOrderedAscending : NSOrderedDescending);
}
#end
a NSDictionary is not ordened, so it doens't matter in what order you construct a NSDIctionary.
a NSArray is ordened. If you want to have the NSDictionary ordened in memory, you should somehow make a NSArray of key value pairs. You can also return two NSArrays with corresponding indeces.
If you only want to iterate over the elements way, you can iterate over a sorted array of keys (this is what koregan suggests).