I got a NSMutableArray which I want to add strings to. I need to check if that string already exists on the array before adding ths string to it. Does anyway know a good way to do it?
Thanks!
If order doesn't matter, the simplest way would be to switch to using an NSMutableSet. Sets are unordered and NSSets and NSMutableSets can contain each object only once. Only a counted set (NSCountedSet) can contain the same object multiple times.
If order does matter, continue using NSMutableArray, and use -containsObject: to check that the object is not already there before adding it.
Edit: And as of Lion, we now have NSOrderedSet and NSMutableOrderedSet! Chocolate in my peanut butter! Peanut butter in my chocolate!
Like an unordered set, you get fast membership testing, the prohibition of duplicates, and, of course, set operations (union, intersection, etc.). Like an array, you get ordered access, including both reliable-order enumeration and access by index.
So, if you need to reject duplicates but at the same time preserve order, Lion's NS{,Mutable}OrderedSet classes are for you.
If you just want to do what you stated once, just use containsObject:, as in
if (![myArray containsObject:theObject]) [myArray addObject:theObject];
Note that this does a linear search through the array, and thus isn't appropriate if you're going to be using this operation a lot. If you are, and you don't need ordering, you should use NSSet. If you do need ordering, you could use both an NSArray and NSSet that are kept in sync (e.g. always add/delete the same object from both collections at the same time).
I preferred the NSPredicate which describe in here
In short
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"Nick", #"Ben", #"Adam", #"Melissa", nil];
NSPredicate *bPredicate = [NSPredicate predicateWithFormat:#"SELF MATCHES[c] %#", your_search_key];
NSArray *beginWithB = [array filteredArrayUsingPredicate:bPredicate];
if ([beginWithB count] > 0)
{
//Handle duplicate
}
Related
I have an array for users, I want create a contact list.
I'm sorting objects and keys alphabetically.
NSArray *sortedKeys = [[contactListDictionary allKeys] sortedArrayUsingSelector: #selector(caseInsensitiveCompare:)];
NSArray *objects = [contactListDictionary objectsForKeys:sortedKeys notFoundMarker:[NSNull null]];
After this, I can initialize dictionary again with sorted keys and
objects.
contactListDictionary = [NSMutableDictionary dictionaryWithObjects:objects forKeys:sortedKeys];
But it did not sorting keys and objects. They are mixing in NSMutableDictionary, what can I do for this situation ?
You can't do anything about this, that is, not in the way you are now trying to.
The NSDictionary class does not allow you to tell it in what order to store its keys; that's just not the way it is implemented.
But you already found out that you can get the keys, in the desired order, in an array. There should be no need for the dictionary itself to be ordered, its purpose is to give you a fast lookup mechanimsm, and it does. If you want, for example, to display the keys in order, simply create a (temporary) array created the way you already now how to.
My code looks like:
NSPredicate* pred = [NSPredicate predicateWithFormat:#"(title IN %#)", prev_set];
NSSet* remainingSet = [goal_set filteredSetUsingPredicate:pred ];
Where prev_set and goal_set are two different sets. "title" in a property in the objects contained in both sets.
My objective is so that all goals are rejected if they have previously been met (each goal having a unique title).
If I have a predicate like "title in %#" ALL of the objects in goal_set are rejected. If I use "NOT("title in %#)", then NONE of the objects in goal_set are rejected.
If I print out both sets, I see that they clearly have some (but not all) objects in common (that is, their title is the same).
Am I completely confused about the use of "IN" for NSPredicates? How would I accomplish my objective?
Try [NSPredicate predicateWithFormat:#"NOT (title IN %#.title)", prev_set].
Your predicates assume that prev_set contains NSString objects, but according to your description, it contains objects that just have a string property, so it will never contain the titles themselves. I hope that makes sense.
By definition, removing the intersection of two sets A and B from A is the relative complement of B in A, also called set difference, i.e. A \ B.
Luckly NSMutableSet features the set operation that you need, namely minusSet:
NSMutableSet *mutableGoalSet = [goal_set mutableCopy];
[mutableGoalSet minusSet:prev_set];
Of course, for this to work, you will have to implement isEqual and hash methods of your goal objects in order to make them identifiable by their title. (relevant: Best practices for overriding isEqual: and hash)
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.
I'm trying to determine if two NSSets are "equal" but not in the sense of isEqualToSet. Items in the two sets are the same class but are not the same object, or even references to the same object. They will have one property that is the same though - let's call it 'name'.
Is my best bet in comparing these two sets to do a simple set count test, then a more complex objectsPassingTest: on each item in one set, making sure an item with the same name is in the other set? I'm hoping that something simpler exists to handle this case.
I had the same problem, but I needed to compare multiple properties at the same time (class User with properties Name and Id).
I resolved this by adding a method returning an NSDictionary with the properties needed to the class:
- (NSDictionary *)itemProperties
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:self.name forKey:#"name"];
[dict setObject:self.id forKey:#"id"];
return dict;
}
and then using valueForKey: as Kevin Ballard mentioned:
BOOL userSetsEqual = [[userSet1 valueForKey:#"itemProperties"]
isEqualToSet:[userSet2 valueForKey:#"itemProperties"]];
... where userSet1 and userSet2 were the NSSets that contained User objects.
You could just call valueForKey: on both sets and compare the results.
if ([[set1 valueForKey:#"name"] isEqualToSet:[set2 valueForKey:#"name"]]) {
// the sets match your criteria
}
Looking through the documentation, it seems that there is no way to really handle this special case of yours. You're going to have to write some custom code to handle this. Personally, I would recommend using -sortedArrayUsingDescriptors: and then comparing the arrays, but that's just me. You could also go enumerate through one set, then narrow down the other using -filteredSetUsingPredicate: and get its count.
Whichever method you use, consider the fact that its probably not going to be super efficient. This might be unavoidable, but there are probably ways to go about it that are better than others. Food for thought.
I'm currently enumerating through NSMutableArray (or NSMutableSet) elements to find duplicates and remove them.
For example, if array/set has values [#"a", #"b", #"b", #"c"], the end result should be [#"a", #"b", #"c"].
Since I'm comparing NSStrings, I'm using isEqualTo: method to check if strings are equal.
Is there a more efficient way to do remove duplicate entries than to loop through all of them and check if duplicate exists?
An NSSet does exactly what you're trying to do: it is a (unordered) collection of unique items. So, you can find the unique items in your array like so:
NSSet *uniqueElements = [NSSet setWithArray:myArray];
// iterate over the unique items
for(id element in uniqueElements) {
// do something
}
NSSet most likely uses a hash algorithm to make insertion O(1) (compared to O(n^2) to check if each item is unique by iteration), but the Apple documentation does not make such a guarantee so you probably shouldn't count on that implementation detail.
If, for some reason you need to keep the unique items in a sorted (ordered) collection, you can turn the set back into an array with -[NSSet allObjects] and then sort the resulting array.
An NSSet or NSMutableSet will guarantee that you don't have duplicate objects. It will work for NSStrings as in your example, but for your own classes keep in mind what do you mean by "equal" and implement the hash and isEqual: methods accordingly.
A set never contains duplicate elements, so simply creating an NSMutableSet should guarantee uniqueness of values.
Only this line of code will work fine .
NSSet *mySet = [NSSet setWithArray:myArray];
now mySet will have unique elements.