"NSSet allObjects" does random ordering? - objective-c

I have the following code:
self.temporaryImageArray = [(NSSet *)[[array objectAtIndex:0] images] allObjects]
Array holds an Band object from my CoreData model. It has an NSSet as a property called "images".
Now I use this temporaryImageArray to determine via timestamps whether or not the images need to be updated. I have come across some very random behavior and my question now is:
Does [NSSet allObjects] return the Objects from the Set randomly in no order?
Is there any way to prevent this or to have it return it in order? It would lessen the complexity of my code a lot.

NSSet is an unordered collection. It has no idea what the "order" of its objects are. Therefore, when you call -allObjects, it returns them unordered.
Note that the documentation on -allObjects states:
An array containing the set’s members, or an empty array if the set has no members. The order of the objects in the array isn’t defined.
(emphasis mine)

A set does not have order. However, in 10.7 (Lion), there is an NSOrderedSet class. This isn't available in iOS 4.0.

NSOrderedSet and NSMutableOrderedSet are now available in iOS 5 just so you're kept up to date.
Here's the link

Related

Array from set: why does NSSet use allObjects, while NSOrderedSet uses array?

In Foundation, if I want to convert a set to an NSArray, I can use:
-[NSSet allObjects]
-[NSOrderedSet array]
Why are these different?
Speculation, but:
Because when NSSet was created the only other major collection type was NSArray, which was (and still is, largely) the most common collection type. So a method called "allObjects" would obviously return an NSArray.
When NSOrderedSet was added much more recently, it had to deal with the existence of prior collections - primarily, NSArray and NSSet. So an "allObjects" method would be ambiguous. Ergo it has two methods, -array and -set.
And/or, the -array and -set methods return proxies to what are likely the same or similar classes used internally. So in a functional sense they're a little different - those proxies will see mutations made on the original NSOrderedSet. -allObjects on the other hand does not - it genuinely creates an independent array, since its internal storage is likely a hashtable or similar that isn't readily proxied.
While there are other differences†, .allObjects does not imply a definite ordering, and .array does; and that's exactly what you are getting.
† .array returns a live proxy of the underlying NSOrderedSet, and if the underlying ordered set changes, the proxy will change with it.
Also... The NSArray returned by 'allObjects' is a copy of the values in the set.
But the NSArray returned by 'array' is a proxy of the objects in the set.
Thus if you change the value of any object in the set, you will change the value of the object in the array. A copy of the ordered set is not being made. So the two properties have different names because they do different things.

Why does NSOrderedMutableSet add objects whose data is the same?

I'm creating objects and adding them to a set using -[NSOrderedMutableSet addObject:], but I discovered that only duplicates of the objects themselves are checked for -- the object pointer's address presumably, and that it's possible to add multiple objects that have identical content.
For example:
SomeObject* object = [SomeObject alloc] initWithStuff:stuff];
SomeObject* object2 = [SomeObject alloc] initWithStuff:stuff];
[set addObject:object];
[set addObject:object];
[set addObject:object1];
[set addObject:object2];
The count will be 2.
This makes me wonder what the point of these classes is? Under what circumstances might one have an object and not know if the object itself had already been added to a collection, rather than the data contained within the object?
Whats the easiest way (or what class should I use) to use to ensure the set only contains one of each object based on content?
The way you are looking is the right way, you are forgetting a small detail: how could the NSMutableOrderedSet class know about which instances of SomeObject contain same values?
The answer is simple: you must provide your own implementations of
- (BOOL)isEqual:(id)anObject
- (NSUInteger)hash
So that your instances will return true when compared with same internal values, and two instances with same data will have same hashcode.
Apart from this sets are rather useful because they give you better complexity on checking if an instance is contained in a set or not, and you can quickly do many logical operations on them, like intersection, union, difference and whatever.
If it is a custom object you have, you'd have to implement your own isEqual: and hash method to check for equality and prevent duplicates in the set.

Merge NSMutableArray with a NSArray, filtering the duplicates

I have two arrays, an NSMutableArray and an NSArray. The NSMutableArray is the "store", it stores results from a source of NSArrays. Every 5 minute, a new NSArray comes in and the data needs to be filtered and sorted.
Sorting by date is pretty easy, so I managed to get the NSArray sorted by NSDate. Sorting the other array is not necessary, as it would only cause confusion with the user.
What I want to do: the NSArray has a lot of different objects that all respond to -[object name], returning an NSString. The NSArray needs to be merged into the NSMutableArray, only adding new objects.
The merging itself is no problem, but performance is. The NSMutableArray can contain up to 3000 items, and the NSArray can contain up to 250 items, although usually only 5 or 6 of these have to be merged into the NSMutableArray.
So, my question is: how do you merge two arrays in Objective-C, filtering the duplicates, without iterating (250*3000) times?
Tom
Edited to clarify something
The "duplicate" objects are objects that are duplicate to the user but not to the code. They have the same name, but not the same address.
More clarification: #"value" != #"value" // true
Is name a property of the objects being stored in the arrays? If so, you could use a fairly simple NSPredicate to filter the immutable array before adding the results to the mutable one. Here's an example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NONE name == %#.name", mutableArray];
resultsArray = [immutableArray filteredArrayUsingPredicate:predicate];
[mutableArray addObjectsFromArray:immutableArray];
How about this:
[mutable removeObjectsInArray:newArray];
[mutable addObjectsFromArray:newArray];
It isn't the fattest, but is easy to implement :)
Edited to remove some stupidity (left plenty, though)
A couple of options:
Remove all matching objects from the NSMutableArray using removeObjectIdenticalTo. This requires iterating through the smaller array, but as you note they're commonly small. Then,
Add all of the items from the new array using addObjectsFromArray
Or... well, it actually might be faster to instead:
Iterate through the new array looking for matches with indexOfObjectIdenticalTo, using addObject to add in non-matching objects.
Costly either way, but doable.
I would probably start by creating a new mutable array which contains the contents of your NSMutableArray and NSArray. Then, sort the new array based on the name property and then run through the array once, only pulling out the unique items.
Can you use NSSet and NSMutableSet instead? That could help deal with the duplicates issue.
Edit:
Based on your comments, you could use an NSSet to check for object membership quickly, in addition to your array. It'd require a bit more memory, but if you don't mind that, it could allow you to check really fast. You'd have your NSMutableArray backing store, and then an NSSet to keep track of object membership. You'd maintain the invariant that the NSMutableArray does not contain duplicates. You could use code like this:
// Assume that arrayStore is an NSMutableArray * instance variable
// Also, storeSet is an NSMutableSet * ivar
- (void)addObjectsFromArray:(NSArray *)data
{
for (id item in data) {
if (![storeSet member:item]) {
// Will have to keep arrayStore sorted somehow
[arrayStore addObject:item];
[storeSet addObject:item];
}
}
}
You only have to iterate through the NSArray. I'm not sure how NSSet is implemented off the top of my head, but checking for membership won't be an O(n) operation like it is for an unsorted array.
It's not the most efficient method, but it works well with what you already have in place, with minor modifications.
There are likely many ways to dramatically improve performance, but to be able to suggest any, we really need to know more about what the objects in the arrays "are": what do they represent? How are they being used? (For example, are the items in the store array being displayed in a table view?)
NSMutableDictionary, NSMutableSet, etc. could be combined with NSMutableArray to organize and implement the model in an efficient manner.
For example, let's say we know the object represents a person: MDPerson. A person has a gender, a date of birth, a name, a unique id, and a set of attributes that can change. Given this higher level understanding of what the object represents, we know that 2 people are equal only if their unique ids are the same (in other words, 2 different people can have the same name, gender, and date of birth). Let's say that your main NSMutableArray is made up of a list of 3000 people. The incoming array is made up of 500 people which are already in the main NSMutableArray. A few of these 500 people instances might have "updated" attributes, which means that their instance in the main array needs to be updated with that info.
Given that understanding, it's clear that the main list should be implemented as an NSMutableDictionary rather than an NSMutableArray. In the dictionary, the person's unique id would be the key, and their person instance would be the value for the key. You could then loop through the incoming array of 500 persons only once:
// main dictionary is called personIDsAndPersons
for (MDPerson *person in incomingPersons) {
MDPerson *existingPerson = [personIDsAndPersons objectForKey:[person uniqueID]];
// if nil, the person doesn't exist
if (existingPerson) {
// update the existing person's attributes
[existingPerson setUniqueAttributes:[person uniqueAttributes]];
}
}
Again, without knowing more of the details or having a higher level understanding of what the objects are, we're really just shooting in the dark.
You mention that 2 items are only the same if they have the same name. So, does that mean that each item in the main array of 3000 objects each have a unique name? If so, you could use an NSMutableDictionary to allow access to the objects in an efficient manner by having the keys in the dictionary be the name and the values be the object instance. You could then use a separate NSMutableArray that's used merely for display purposes: it allows an ordered, sorted organization of the same objects that are stored in the NSMutableDictionary. Remember that when you add an object to an array or a dictionary, normally you're not creating a new copy, you're just retaining the existing object.

How can i make NSMutableArray only accept unique values?

NSMutableArray *sectionTitles;
[sectionTitles addObject:due];
How do I just add unique values to an array?
See Rudolph's Answer
My old answer below is now outdated and has been for awhile. Rudoph's reference to NSOrderedSet / NSMutableOrderedSet is the correct one since these classes were added after this Q and my A.
Old Answer
As Richard said, NSMutableSet works well but only if you don't need to maintain an ordered collection. If you do need an ordered collection a simple content check is the best you can do:
if (![myMutableArray containsObject:newObject])
[myMutableArray addObject:newObject];
Update based on comment
You can wrap this in a method like -addUniqueObject: and put it in an NSMutableArray category.
More 2012-ish answer (in case someone stumbled upon this in the future):
Use NSOrderedSet and NSMutableOrderedSet.
A note about performance right from NSOrderedSet docs:
You can use ordered sets as an alternative to arrays when the order of elements is important and performance in testing whether an object is contained in the set is a consideration— testing for membership of an array is slower than testing for membership of a set.
Use NSMutableSet, it is best for these situations
iOS Reference
Mac OSX Reference

traverse Core Data object graph with added predicate?

I want to load a client object and then pull their related purchase orders based on whether they have been placed or not, purchase orders have an IsPlaced BOOL property.
So I have my client object and I can get all purchase orders like this, which is working great:
purchaseordersList =[[myclient.purchaseorders allObjects] mutableCopy];
But ideally I would actually like 2 array's - one for each order type: IsPlaced=YES and IsPlaced=NO
How do I do that here? Or do I need to do another fetch?
First, there is no reason to be turning the set into an array unless you are sorting it and there is no reason to be turning that array into a mutable array. Did you get that from some example code?
Second, you can filter an array or a set by using a predicate so you can create two sets (or arrays) easily via:
NSSet *placed = [[myclient purchaseorders] filteredSetUsingPredicate:[NSPredicate predicateWithFormat:#"isPlaced == YES"]];
NSSet *notPlaced = [[myclient purchaseorders] filteredSetUsingPredicate:[NSPredicate predicateWithFormat:#"isPlaced == YES"]];
If you are wanting to use this for a UITableView then look into a NSFetchedResultsController instead. It will save you a LOT of boiler-plate code.
Do you remember what example code you got that from? Been seeing that -mutableCopy a lot lately and would love to quash it. :)